I am using Google Cloud Messaging and to manage the users devices server side im storing the GCM Registration ID and the Users Device ID in my Database. You can get the Device ID like that:
public static String getDeviceID(Context context) {
final String deviceId = ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId();
if (deviceId != null) {
return deviceId;
} else {
return android.os.Build.SERIAL;
}
}
Since Android 6.0 i have a problem with that because to read the Devices ID you need to use the dangerous permission android.permission.READ_PHONE_STATE. So when the user uses my App and the automatic background process to register for the GCM Service he get's automatically asked to grant permission for make and manage phone calls which, in my opinion, is totally confusing for the user. He would not understand why the app needs to make and manage phone calls and i also dont want another dialog box to explain the user why the app needs this permission. It would make the app complicated and somehow frustrating for some users.
I want to get around this so i have to ask if there is any alternative way to uniquly identify a users device without needing any dangerous permission.
It has to fullfill following requirements:
It must be unique
It must be always the same id for a single device also after uninstalling and reinstalling the app
The reason for that is simple:
The user can have multiple devices
There can be multiple users on one device
Ofcourse i could just set my target SDK to < 23 but some day you have to update your system anyway when you need new features from the 6.0 or greater API.
I also dont want to give the user the freedom of decision for that purpose because if the user does not grant the READ_PHONE_STATE permission it would mess up the whole live update, messaging and synchronization system of the App.
Related
Before Android 10, I was using the TelephonyManager API to retrieve that info, but it's no longer working in Android 10.
I'm facing the same problem, but, in my case, I have a kind of "backup" code that returns a UUID.
Here is a code that you could use:
String uniqueID = UUID.randomUUID().toString();
This code is usefull if you want a "instalation unique identifier" but, doesn't works as a device unique identifier because if the user unistall and re install your app, the UUID returned will be different than the last one.
In my case, I use the UUID.nameUUIDFromBytes to generated a UUID by a given "name" and I'm using the Settings.Secure.ANDROID_ID as the "name" for the UUID. Using this method you "grantee" the returned UUID will be the same, UNLESS the user do a factory reset.
Here is the code:
String androidId = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.ANDROID_ID);
UUID androidId_UUID = UUID
.nameUUIDFromBytes(androidId.getBytes("utf8"));
String unique_id = androidId_UUID.toString();
Until here, everything seens ok, but the problem is: since Android 10 was released, Google doesn't recommend the uses of any kind of "hardware indentifier" and this includes the Settings.Secure.ANDROID_ID. This actually is my concern, because in the company that I work for, we use the IMEI or this UUID to identify our customers users and define if an user is trying to log in more than one device, which is not allowed by our rules, and to build some statics. If the UUID isn't unique for the same device we'll have to review all of our user access control.
Here is the Android Developers link about unique identifiers good pratices.
https://developer.android.com/training/articles/user-data-ids
And here is the same link, but with an anchor where Google describes some use cases and the best option of unique identifier for each one.
https://developer.android.com/training/articles/user-data-ids#common-use-cases
None of use cases fit with mine, so I'm still loking for a better solution.
I hope this could help someone.
From the Android Developers Documentation Website
Starting in Android 10, apps must have the READ_PRIVILEGED_PHONE_STATE privileged permission in order to access the device's non-resettable identifiers, which include both IMEI and serial number.
Third-party apps installed from the Google Play Store cannot declare privileged permissions.
If your app doesn't have the permission and you try asking for information about non-resettable identifiers anyway, the platform's response varies based on target SDK version:
If your app targets Android 10 or higher, a SecurityException occurs.
If your app targets Android 9 (API level 28) or lower, the method returns null or placeholder data if the app has the READ_PHONE_STATE permission. Otherwise, a SecurityException occurs.
If you try to access it, it throws the below exception:
java.lang.SecurityException: getImeiForSlot: The user 10180 does not meet the requirements to access device identifiers.
I'm making a pair of website-based apps for both Android and iOS interfaces, and I'm struggling with a part of it. Perhaps you guys could help me out!
I'm using Android Studio and Xcode, and launching the website through WebKit and WK WebView respectively. It's super simple, just an app which calls a website into it directly. No external navigation, nothing but a full-page website. And this part is working great!
But I do have one problem! I don't want my users to get consistently logged out if they close the app, or after a few hours of not using it. I'd like it to stay logged in for them, or to automatically log-in when they use it.
The maker of the website has given me a way to do this through the URL.
Basically, my URL currently is set up like "https://URL.com/x/y/z" and it goes to the website, and that is great, but I need to set it up to be "https://url.com/x/y/z/[insert user's IMEI or UDID here]". That unique ID from their Android device will keep them logged in. I've tested it using my own device with my own IMEI and it works great, but obviously using one specific identifier for everyone will not work. I just need it to call the specific user's IMEI or UDID into the URL, to complete it.
How should I go about this?
I am assuming that you are talking about an Android app that visualizes a website in an activity. On Android, you can retrieve the device's IMEI OR MEID string by calling:
android.telephony.TelephonyManager.getDeviceId().
But be warned, this requires that you add a permission to your manifest:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
You should inform your users why you are requesting this permission.
For iOS/Xcode, you can get the device UUID via UIDevice:
// Swift 4
let uuid = UIDevice.current.identifierForVendor!.uuidString
If you are targeting iOS 11 or newer, Apple introduced Device Check to get a device specific token. I personally haven't used it, but it sounds like it's use case is similar to what you're looking for.
Update:
From your comment, here is how'd you include it in the url string.
let urlString = "url.com/x/y/z/" + uuid
What I need feels fairly straightforward and it's so frustrating that the Android for Work API doesn't appear to provide it out-of-the-box.
I am trying to create an Android DPC app to own and manage the Work Profile. (NOT device owner).
When you provision the work profile, you get two instances of your application. One badged running under the Work Profile and the other unbadged running under the Primary Profile.
I am trying to find out some three things:
Is the Work Profile already provisioned on the device?
If so, is it my app that owns the profile? If not which app does?
Is the Work Profile active?
The reason is, even in Google's own sample app (see image) it doesn't try to establish this and initiates provisioning even when there is already a work profile and the app is actually the owner!
Is the Work Profile already provisioned on the device?
If so, is it my app that owns the profile? If not which app does?
This code will work when run under the primary user. A profile owner for a primary user will be the work profile. It will log your own package if your app owns it.
DevicePolicyManager manager =
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
List<ComponentName> activeAdmins = manager.getActiveAdmins();
if (activeAdmins != null){
for (ComponentName admin : activeAdmins){
String packageName = admin.getPackageName();
if (manager.isProfileOwnerApp(packageName)){
Log.d(TAG, "Work Profile is: " + packageName);
}
}
}
Use this if you just want to check if your app is the profile owner within your app.
manager.isProfileOwnerApp(getApplicationContext().getPackage());
Is the Work Profile active?
If isProfileOwnerApp() returns true for any package under the primary user, the work profile is active and owned by that package.
Secondary users can also be provisioned with a profile owner on a device that supports multi-users, but I have not seen this implemented by an EMM yet. A device owner would need to assign your package's component as the profile owner of a secondary user, so it is probably safe to say that won't happen. But if it does, your app should work just like a work profile, but in the context of a secondary user as a managed profile.
* EDIT (6/15/18) *:
I tested your scenario on an Android O device and I did not get the same behavior. After provisioning a work profile from TestDPC, TestDPC detected that a managed profile had already been provisioned and would not let me provision again.
What version of Android are you developing on?
I dug into TestDPC and found some code, modified for your scenario, that may help you. Unfortunately for Android M and below, TestDPC will not detect that the device had already been provisioned with a work profile and will just attempt it again. Additionally, I didn't find a way to detect who that profile owner is, your app or another app. But I hope this helps!
/**
* #param context Calling activity's context
* #return true, if work profile provisioning is allowed
*/
#TargetApi(Build.VERSION_CODES.N)
public static boolean isProvisioningAllowed(Context context) {
if (BuildCompat.isAtLeastN()) {
DevicePolicyManager dpm = (DevicePolicyManager) context
.getSystemService(Context.DEVICE_POLICY_SERVICE);
return dpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_DEVICE);
}
else {
return true;
}
}
Create an activity with a special intent filter and set android:enabled="false". Override onCreate in order to set a result and then immediately finish.
Within your implementation of DeviceAdminReceiver::onProfileProvisioningComplete, enable your special activity with PackageManager::setComponentEnabledSetting and add a cross-profile intent filter so it can be called from the primary profile.
When your main activity opens:
If DeviceProfileManager::isProfileOwnerApp returns true, you're running in your managed profile.
Otherwise, use startActivityForResult to start an Intent that matches your cross-profile intent filter. Use Intent::setPackage to ensure that only your package can respond to it.
If startActivityForResult throws an exception, your managed profile is not set up. If UserManager::getUserProfiles returns only one profile, no managed profile is setup; otherwise, some other app's managed profile is setup.
If your managed profile is setup, you'll get a call to onActivityResult including any data you send to yourself, such as the UserHandle for the managed profile.
SITUATION
I have a set of functionalities in my app which changes based on the store it is installed from. E.g. I want to have a more restricted set of advertisements displayed for family audiences and children to be eligible for the Google Play for Education category. In other stores i still want to restrict but don't want to be as stringent as I will be in filtering out the ads.
General observation at my end is that if I opt-in for "Google Play for Education" category it takes a few more hours to get published because of the following (as stated on the developer console):
Checking this box submits this app for inclusion in the "educator
recommended" section of Google Play for Education. The final decision
on which apps to recommend is made by a 3rd party network of teachers.
If your app is selected, we will notify you by e-mail. If not, your
app will still be searchable in Google Play for Education.
Now before the app gets published in this category the network of teachers, I assume, download and test/verify if the guidelines are met and there are no violations.
PROBLEM
To differentiate between the store installed from I'm obviously using this:
PackageManager packageManager = context.getPackageManager();
String installer = packageManager.getInstallerPackageName(packageName);
if (installer == null) installer = ""; //to avoid NPE
if (installer.equals("com.android.vending")) {
//It is installed from Google Play store
//PROBLEM: THIS DOES NOT SEEM TO BE THE PACKAGE NAME RETURNED
//WHEN GOOGLE PLAY REVIEWERS/TESTERS ARE USING THE APP
}
else ...
....
....
//similarly handling other stores here
....
....
....
//After that also checking by installed app stores
....
....
What is happening: After being published things app properly identifies that it is downloaded from play store i.e. it gets com.android.vending as the installerPackageName. But, when it is being reviewed or tested by the network of teachers it appears to be getting a different installerPackageName. This is causing the app to think it has been downloaded from an app store other than Google Play Store. And because of this my app is rejected from the Education category. I need to know this installer package name to handle the scenario correctly.
How do i know this: I have a dedicated ad unit id to use when the detected app store is Google Play and all requests post successful publication (i.e. from the regular play store users) come to this google dedicated ad unit id. But, in the short span of time after submitting the app/update and before the app or and update is published, a few requests come to the non-google ad unit ids, causing the app to fail adherence to the guidelines to be eligible for "Google Play for Education" category. Because, the level of ad filtering in the non-google ad unit ids is slightly less. Hence the teachers evaluating/testing the app see some ads that they think are not as per guidelines and reject it.
Also, here is an article to support the fact that the app gets reviewed manually as well as by automated script before it is actually published to the store.
Current fix or limitation: I've disabled all other ad networks and have to use only admob. The setting of filters even at the strictest level in other ad networks doesn't seem to filter all ads that Google reviewers think are not suitable for children and family audiences. When using only admob the process is smooth and I always qualify.
What I'm looking for to overcome this problem: If i get to know the installerPackageName that is returned when the app is installed from where ever the reviewing network of teachers install the app from, I can handle that case exactly as i handle when i get com.android.vending and everything will be just fine.
I could not find any documentation or reference to obtain this information.
Also, if there is any other way i can identify it the app is in pending publication stage, I force all requests to go to the google ad unit id.
ASSUMPTION: There is a separate installer app for the reviewers and testers (automated/manual what ever it may be in the background) whose installerPackageName is NOT com.android.vending. If there is some Google guy around and can help confirm this (if allowed by Google), do comment. :-)
Other possibilities which i do not want to go with
Disable all other networks manually while the app is in pending publication phase and re-enable them once published. But, I don't want to do this because this would be like bluffing google and I don't want to go that way. I want my app logic to take care of it so that the same thing that is reviewed is let out in market.
I permanently stick with only admob. But this would be foolish as there are no such restrictions in other stores that I'm publishing my apps to and I will terribly lose on fill rate.
I there anyone who has had this issue before OR knows the installerPackageName for the review's download place OR knows how to determine if the app is currently in 'pending publication` state on playstore?
I can also possibly filter all by packagenames starting with com.android or com.google, but I want to keep that as last option. Also, would like to know if the installerPackage name is not set at all in case of those users. In that case I'll need to look at a completely different situation to handle the situation.
I find one thing that can help you in this case. It's analytics. Just create your custom event eg. INSTALLER_STRING in some of analytics systems and log that event when appropriate. Here is the example of event logging in Fabric Answers.
public static final String EVENT_OPEN_TOP_TRENDS = "EVENT_OPEN_TOP_TRENDS";
public static final String TOP_TRENDS_TYPE = "TOP_TRENDS_TYPE";
public static final String TYPE_TOP_TRENDS_IMAGES = "TYPE_TOP_TRENDS_IMAGES";
public static void logEvent(String eventId, String attributeName, String name) {
Answers.getInstance().logCustom(new CustomEvent(eventId).putCustomAttribute(attributeName, name));
}
logEvent(EVENT_OPEN_TOP_TRENDS, TOP_TRENDS_TYPE, TYPE_TOP_TRENDS_IMAGES);
Later on, you can see what are the sources your app was installer from on fabric website.
Check which results you will show using this log for all app you use do detect installer package name:
final PackageManager pm = getPackageManager();
//get a list of installed apps.
List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo packageInfo : packages) {
String is = pm.getInstallerPackageName(packageInfo.packageName);//getPackageName());
Log.d(TAG, (packageInfo.packageName==null?"":packageInfo.packageName) + " : " + (is==null?"":is));
}
You will see the full list of installed apps & package sources, or log this data in your way your want.
EDIT
#Virus, i just want to give you a simple idea. if you want to know package installer name, you can just grab this data when you app is launched and send it to your own server using simple http GET request in order to detect the installer name. Republish you apo with this simple frabber. I think this the one solution.
I've been looking for a solution to this problem for a while (days, not minutes), but it eludes me quite effectively.
Please note that this is NOT a question about starting up the registration procedure. This must happen automatically without any user interaction.
I would like to add a Google account to my custom device (1000's of them). The account will mostly be used to activate Google Play store on the device so that the app can update when newer versions are available.
My existing code (the shortest snippet of those I tried):
AccountManager mgr = AccountManager.get(this);
Account acc = new Account("email#gmail.com", "com.google");
mgr.addAccountExplicitly(acc, "password", new Bundle()));
naturally yields a
java.lang.SecurityException: caller uid 10047 is different than the authenticator's uid
So how would I go about actually achieving this? My device is rooted so that's not an obstacle if it's the only way.
It is not possible to add/create a Google account using addAccountExplicitly(). You can only add accounts for your own services. even your device is rooted because it will rejected by Google web server. For more detail check this link
Warning: this solution doesn't work well. See comments for explanation.
Well, as it turns out, this is not something easily solved. I ended up registering one device, then pulled the users file from it. Location of users file : /data/system/users/0/accounts.db (if there are multiple user profiles on the device, the last directory may differ according to profile in question).
I stored this file into my app's assets (gzipped, make sure the extension is not something.gz because that gets lost during packaging - didn't bother checking out why).
First I check if my user already exists:
AccountManager mgr = AccountManager.get(this);
for (Account acc: mgr.getAccountsByType("com.google")) {
if (acc.name.equalsIgnoreCase("email#gmail.com"))
return;
}
If it does, I just skip the step. Otherwise I unpack the users file and overwrite existing one (using su). I then also do a reboot to make sure changes are registered.