I need to use a unique ID for an Android app and I thought the serial number for the device would be a good candidate. How do I retrieve the serial number of an Android device in my app ?
TelephonyManager tManager = (TelephonyManager)myActivity.getSystemService(Context.TELEPHONY_SERVICE);
String uid = tManager.getDeviceId();
getSystemService is a method from the Activity class. getDeviceID() will return the MDN or MEID of the device depending on which radio the phone uses (GSM or CDMA).
Each device MUST return a unique value here (assuming it's a phone). This should work for any Android device with a sim slot or CDMA radio. You're on your own with that Android powered microwave ;-)
As Dave Webb mentions, the Android Developer Blog has an article that covers this.
I spoke with someone at Google to get some additional clarification on a few items. Here's what I discovered that's NOT mentioned in the aforementioned blog post:
ANDROID_ID is the preferred solution. ANDROID_ID is perfectly reliable on versions of Android <=2.1 or >=2.3. Only 2.2 has the problems mentioned in the post.
Several devices by several manufacturers are affected by the ANDROID_ID bug in 2.2.
As far as I've been able to determine, all affected devices have the same ANDROID_ID, which is 9774d56d682e549c. Which is also the same device id reported by the emulator, btw.
Google believes that OEMs have patched the issue for many or most of their devices, but I was able to verify that as of the beginning of April 2011, at least, it's still quite easy to find devices that have the broken ANDROID_ID.
Based on Google's recommendations, I implemented a class that will generate a unique UUID for each device, using ANDROID_ID as the seed where appropriate, falling back on TelephonyManager.getDeviceId() as necessary, and if that fails, resorting to a randomly generated unique UUID that is persisted across app restarts (but not app re-installations).
import android.content.Context;
import android.content.SharedPreferences;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import java.io.UnsupportedEncodingException;
import java.util.UUID;
public class DeviceUuidFactory {
protected static final String PREFS_FILE = "device_id.xml";
protected static final String PREFS_DEVICE_ID = "device_id";
protected static volatile UUID uuid;
public DeviceUuidFactory(Context context) {
if (uuid == null) {
synchronized (DeviceUuidFactory.class) {
if (uuid == null) {
final SharedPreferences prefs = context
.getSharedPreferences(PREFS_FILE, 0);
final String id = prefs.getString(PREFS_DEVICE_ID, null);
if (id != null) {
// Use the ids previously computed and stored in the
// prefs file
uuid = UUID.fromString(id);
} else {
final String androidId = Secure.getString(
context.getContentResolver(), Secure.ANDROID_ID);
// Use the Android ID unless it's broken, in which case
// fallback on deviceId,
// unless it's not available, then fallback on a random
// number which we store to a prefs file
try {
if (!"9774d56d682e549c".equals(androidId)) {
uuid = UUID.nameUUIDFromBytes(androidId
.getBytes("utf8"));
} else {
final String deviceId = ((TelephonyManager)
context.getSystemService(
Context.TELEPHONY_SERVICE))
.getDeviceId();
uuid = deviceId != null ? UUID
.nameUUIDFromBytes(deviceId
.getBytes("utf8")) : UUID
.randomUUID();
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
// Write the value out to the prefs file
prefs.edit()
.putString(PREFS_DEVICE_ID, uuid.toString())
.commit();
}
}
}
}
}
/**
* Returns a unique UUID for the current android device. As with all UUIDs,
* this unique ID is "very highly likely" to be unique across all Android
* devices. Much more so than ANDROID_ID is.
*
* The UUID is generated by using ANDROID_ID as the base key if appropriate,
* falling back on TelephonyManager.getDeviceID() if ANDROID_ID is known to
* be incorrect, and finally falling back on a random UUID that's persisted
* to SharedPreferences if getDeviceID() does not return a usable value.
*
* In some rare circumstances, this ID may change. In particular, if the
* device is factory reset a new device ID may be generated. In addition, if
* a user upgrades their phone from certain buggy implementations of Android
* 2.2 to a newer, non-buggy version of Android, the device ID may change.
* Or, if a user uninstalls your app on a device that has neither a proper
* Android ID nor a Device ID, this ID may change on reinstallation.
*
* Note that if the code falls back on using TelephonyManager.getDeviceId(),
* the resulting ID will NOT change after a factory reset. Something to be
* aware of.
*
* Works around a bug in Android 2.2 for many devices when using ANDROID_ID
* directly.
*
* #see http://code.google.com/p/android/issues/detail?id=10603
*
* #return a UUID that may be used to uniquely identify your device for most
* purposes.
*/
public UUID getDeviceUuid() {
return uuid;
}
}
String serial = null;
try {
Class<?> c = Class.forName("android.os.SystemProperties");
Method get = c.getMethod("get", String.class);
serial = (String) get.invoke(c, "ro.serialno");
} catch (Exception ignored) {
}
This code returns device serial number using a hidden Android API.
String deviceId = Settings.System.getString(getContentResolver(),
Settings.System.ANDROID_ID);
Although, it is not guaranteed that the Android ID will be an unique identifier.
There is an excellent post on the Android Developer's Blog discussing this.
It recommends against using TelephonyManager.getDeviceId() as it doesn't work on Android devices which aren't phones such as tablets, it requires the READ_PHONE_STATE permission and it doesn't work reliably on all phones.
Instead you could use one of the following:
Mac Address
Serial Number
ANDROID_ID
The post discusses the pros and cons of each and it's worth reading so you can work out which would be the best for your use.
For a simple number that is unique to the device and constant for its lifetime (barring a factory reset or hacking), use Settings.Secure.ANDROID_ID.
String id = Secure.getString(getContentResolver(), Secure.ANDROID_ID);
To use the device serial number (the one shown in "System Settings / About / Status") if available and fall back to Android ID:
String serialNumber = Build.SERIAL != Build.UNKNOWN ? Build.SERIAL : Secure.getString(getContentResolver(), Secure.ANDROID_ID);
The IMEI is good but only works on Android devices with phone. You should consider support for Tablets or other Android devices as well, that do not have a phone.
You have some alternatives like: Build class members, BT MAC, WLAN MAC, or even better - a combination of all these.
I have explained these details in an article on my blog, see:
http://www.pocketmagic.net/?p=1662
Since no answer here mentions a perfect, fail-proof ID that is both PERSISTENT through system updates and exists in ALL devices (mainly due to the fact that there isn't an individual solution from Google), I decided to post a method that is the next best thing by combining two of the available identifiers, and a check to chose between them at run-time.
Before code, 3 facts:
TelephonyManager.getDeviceId() (a.k.a.IMEI) will not work well or at all for non-GSM, 3G, LTE, etc. devices, but will always return a unique ID when related hardware is present, even when no SIM is inserted or even when no SIM slot exists (some OEM's have done this).
Since Gingerbread (Android 2.3) android.os.Build.SERIAL must exist on any device that doesn't provide IMEI, i.e., doesn't have the aforementioned hardware present, as per Android policy.
Due to fact (2.), at least one of these two unique identifiers will ALWAYS be present, and SERIAL can be present at the same time that IMEI is.
Note: Fact (1.) and (2.) are based on Google statements
SOLUTION
With the facts above, one can always have a unique identifier by checking if there is IMEI-bound hardware, and fall back to SERIAL when it isn't, as one cannot check if the existing SERIAL is valid. The following static class presents 2 methods for checking such presence and using either IMEI or SERIAL:
import java.lang.reflect.Method;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.Log;
public class IDManagement {
public static String getCleartextID_SIMCHECK (Context mContext){
String ret = "";
TelephonyManager telMgr = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if(isSIMAvailable(mContext,telMgr)){
Log.i("DEVICE UNIQUE IDENTIFIER",telMgr.getDeviceId());
return telMgr.getDeviceId();
}
else{
Log.i("DEVICE UNIQUE IDENTIFIER", Settings.Secure.ANDROID_ID);
// return Settings.Secure.ANDROID_ID;
return android.os.Build.SERIAL;
}
}
public static String getCleartextID_HARDCHECK (Context mContext){
String ret = "";
TelephonyManager telMgr = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if(telMgr != null && hasTelephony(mContext)){
Log.i("DEVICE UNIQUE IDENTIFIER",telMgr.getDeviceId() + "");
return telMgr.getDeviceId();
}
else{
Log.i("DEVICE UNIQUE IDENTIFIER", Settings.Secure.ANDROID_ID);
// return Settings.Secure.ANDROID_ID;
return android.os.Build.SERIAL;
}
}
public static boolean isSIMAvailable(Context mContext,
TelephonyManager telMgr){
int simState = telMgr.getSimState();
switch (simState) {
case TelephonyManager.SIM_STATE_ABSENT:
return false;
case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
return false;
case TelephonyManager.SIM_STATE_PIN_REQUIRED:
return false;
case TelephonyManager.SIM_STATE_PUK_REQUIRED:
return false;
case TelephonyManager.SIM_STATE_READY:
return true;
case TelephonyManager.SIM_STATE_UNKNOWN:
return false;
default:
return false;
}
}
static public boolean hasTelephony(Context mContext)
{
TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if (tm == null)
return false;
//devices below are phones only
if (Build.VERSION.SDK_INT < 5)
return true;
PackageManager pm = mContext.getPackageManager();
if (pm == null)
return false;
boolean retval = false;
try
{
Class<?> [] parameters = new Class[1];
parameters[0] = String.class;
Method method = pm.getClass().getMethod("hasSystemFeature", parameters);
Object [] parm = new Object[1];
parm[0] = "android.hardware.telephony";
Object retValue = method.invoke(pm, parm);
if (retValue instanceof Boolean)
retval = ((Boolean) retValue).booleanValue();
else
retval = false;
}
catch (Exception e)
{
retval = false;
}
return retval;
}
}
I would advice on using getCleartextID_HARDCHECK. If the reflection doesn't stick in your environment, use the getCleartextID_SIMCHECK method instead, but take in consideration it should be adapted to your specific SIM-presence needs.
P.S.: Do please note that OEM's have managed to bug out SERIAL against Google policy (multiple devices with same SERIAL), and Google as stated there is at least one known case in a big OEM (not disclosed and I don't know which brand it is either, I'm guessing Samsung).
Disclaimer: This answers the original question of getting a unique device ID, but the OP introduced ambiguity by stating he needs a unique ID for an APP. Even if for such scenarios Android_ID would be better, it WILL NOT WORK after, say, a Titanium Backup of an app through 2 different ROM installs (can even be the same ROM). My solution maintains persistence that is independent of a flash or factory reset, and will only fail when IMEI or SERIAL tampering occurs through hacks/hardware mods.
There are problems with all the above approaches. At Google i/o Reto Meier released a robust answer to how to approach this which should meet most developers needs to track users across installations.
This approach will give you an anonymous, secure user ID which will be persistent for the user across different devices (including tablets, based on primary Google account) and across installs on the same device. The basic approach is to generate a random user ID and to store this in the apps shared preferences. You then use Google's backup agent to store the shared preferences linked to the Google account in the cloud.
Lets go through the full approach. First we need to create a backup for our SharedPreferences using the Android Backup Service. Start by registering your app via this link: http://developer.android.com/google/backup/signup.html
Google will give you a backup service key which you need to add to the manifest. You also need to tell the application to use the BackupAgent as follows:
<application android:label="MyApplication"
android:backupAgent="MyBackupAgent">
...
<meta-data android:name="com.google.android.backup.api_key"
android:value="your_backup_service_key" />
</application>
Then you need to create the backup agent and tell it to use the helper agent for sharedpreferences:
public class MyBackupAgent extends BackupAgentHelper {
// The name of the SharedPreferences file
static final String PREFS = "user_preferences";
// A key to uniquely identify the set of backup data
static final String PREFS_BACKUP_KEY = "prefs";
// Allocate a helper and add it to the backup agent
#Override
public void onCreate() {
SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this, PREFS);
addHelper(PREFS_BACKUP_KEY, helper);
}
}
To complete the backup you need to create an instance of BackupManager in your main Activity:
BackupManager backupManager = new BackupManager(context);
Finally create a user ID, if it doesn't already exist, and store it in the SharedPreferences:
public static String getUserID(Context context) {
private static String uniqueID = null;
private static final String PREF_UNIQUE_ID = "PREF_UNIQUE_ID";
if (uniqueID == null) {
SharedPreferences sharedPrefs = context.getSharedPreferences(
MyBackupAgent.PREFS, Context.MODE_PRIVATE);
uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);
if (uniqueID == null) {
uniqueID = UUID.randomUUID().toString();
Editor editor = sharedPrefs.edit();
editor.putString(PREF_UNIQUE_ID, uniqueID);
editor.commit();
//backup the changes
BackupManager mBackupManager = new BackupManager(context);
mBackupManager.dataChanged();
}
}
return uniqueID;
}
This User_ID will now be persistent across installations, even if the user switches devices.
For more information on this approach see Reto's talk here http://www.google.com/events/io/2011/sessions/android-protips-advanced-topics-for-expert-android-app-developers.html
And for full details of how to implement the backup agent see the developer site here: http://developer.android.com/guide/topics/data/backup.html
I particularly recommend the section at the bottom on testing as the backup does not happen instantaneously and so to test you have to force the backup.
Another way is to use /sys/class/android_usb/android0/iSerial in an App with no permissions whatsoever.
user#creep:~$ adb shell ls -l /sys/class/android_usb/android0/iSerial
-rw-r--r-- root root 4096 2013-01-10 21:08 iSerial
user#creep:~$ adb shell cat /sys/class/android_usb/android0/iSerial
0A3CXXXXXXXXXX5
To do this in java one would just use a FileInputStream to open the iSerial file and read out the characters. Just be sure you wrap it in an exception handler because not all devices have this file.
At least the following devices are known to have this file world-readable:
Galaxy Nexus
Nexus S
Motorola Xoom 3g
Toshiba AT300
HTC One V
Mini MK802
Samsung Galaxy S II
You can also see my blog post here: http://insitusec.blogspot.com/2013/01/leaking-android-hardware-serial-number.html where I discuss what other files are available for info.
As #haserman says:
TelephonyManager tManager = (TelephonyManager)myActivity.getSystemService(Context.TELEPHONY_SERVICE);
String uid = tManager.getDeviceId();
But it's necessary including the permission in the manifest file:
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
Unique device ID of Android OS Device as String.
String deviceId;
final TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (mTelephony.getDeviceId() != null){
deviceId = mTelephony.getDeviceId();
}
else{
deviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID);
}
but I strngly recommend this method suggested by Google::
Identifying App Installations
Build.SERIAL is the simplest way to go, although not entirely reliable as it can be empty or sometimes return a different value (proof 1, proof 2) than what you can see in your device's settings.
There are several ways to get that number depending on the device's manufacturer and Android version, so I decided to compile every possible solution I could found in a single gist. Here's a simplified version of it :
public static String getSerialNumber() {
String serialNumber;
try {
Class<?> c = Class.forName("android.os.SystemProperties");
Method get = c.getMethod("get", String.class);
serialNumber = (String) get.invoke(c, "gsm.sn1");
if (serialNumber.equals(""))
serialNumber = (String) get.invoke(c, "ril.serialnumber");
if (serialNumber.equals(""))
serialNumber = (String) get.invoke(c, "ro.serialno");
if (serialNumber.equals(""))
serialNumber = (String) get.invoke(c, "sys.serialnumber");
if (serialNumber.equals(""))
serialNumber = Build.SERIAL;
// If none of the methods above worked
if (serialNumber.equals(""))
serialNumber = null;
} catch (Exception e) {
e.printStackTrace();
serialNumber = null;
}
return serialNumber;
}
I know this question is old but it can be done in one line of code
String deviceID = Build.SERIAL;
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.
Affected methods include the following:
Build
getSerial()
TelephonyManager
getImei()
getDeviceId()
getMeid()
getSimSerialNumber()
getSubscriberId()
READ_PRIVILEGED_PHONE_STATE is available for platform only
public static String getManufacturerSerialNumber() {
String serial = null;
try {
Class<?> c = Class.forName("android.os.SystemProperties");
Method get = c.getMethod("get", String.class, String.class);
serial = (String) get.invoke(c, "ril.serialnumber", "unknown");
} catch (Exception ignored) {}
return serial;
}
Works on API 29 and 30, tested on Samsung galaxy s7 s9 Xcover
I found the example class posted by #emmby above to be a great starting point. But it has a couple of flaws, as mentioned by other posters. The major one is that it persists the UUID to an XML file unnecessarily and thereafter always retrieves it from this file. This lays the class open to an easy hack: anyone with a rooted phone can edit the XML file to give themselves a new UUID.
I've updated the code so that it only persists to XML if absolutely necessary (i.e. when using a randomly generated UUID) and re-factored the logic as per #Brill Pappin's answer:
import android.content.Context;
import android.content.SharedPreferences;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import java.io.UnsupportedEncodingException;
import java.util.UUID;
public class DeviceUuidFactory {
protected static final String PREFS_FILE = "device_id.xml";
protected static final String PREFS_DEVICE_ID = "device_id";
protected static UUID uuid;
public DeviceUuidFactory(Context context) {
if( uuid ==null ) {
synchronized (DeviceUuidFactory.class) {
if( uuid == null) {
final SharedPreferences prefs = context.getSharedPreferences( PREFS_FILE, 0);
final String id = prefs.getString(PREFS_DEVICE_ID, null );
if (id != null) {
// Use the ids previously computed and stored in the prefs file
uuid = UUID.fromString(id);
} else {
final String androidId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
// Use the Android ID unless it's broken, in which case fallback on deviceId,
// unless it's not available, then fallback on a random number which we store
// to a prefs file
try {
if ( "9774d56d682e549c".equals(androidId) || (androidId == null) ) {
final String deviceId = ((TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE )).getDeviceId();
if (deviceId != null)
{
uuid = UUID.nameUUIDFromBytes(deviceId.getBytes("utf8"));
}
else
{
uuid = UUID.randomUUID();
// Write the value out to the prefs file so it persists
prefs.edit().putString(PREFS_DEVICE_ID, uuid.toString() ).commit();
}
}
else
{
uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8"));
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
/**
* Returns a unique UUID for the current android device. As with all UUIDs, this unique ID is "very highly likely"
* to be unique across all Android devices. Much more so than ANDROID_ID is.
*
* The UUID is generated by using ANDROID_ID as the base key if appropriate, falling back on
* TelephonyManager.getDeviceID() if ANDROID_ID is known to be incorrect, and finally falling back
* on a random UUID that's persisted to SharedPreferences if getDeviceID() does not return a
* usable value.
*
* In some rare circumstances, this ID may change. In particular, if the device is factory reset a new device ID
* may be generated. In addition, if a user upgrades their phone from certain buggy implementations of Android 2.2
* to a newer, non-buggy version of Android, the device ID may change. Or, if a user uninstalls your app on
* a device that has neither a proper Android ID nor a Device ID, this ID may change on reinstallation.
*
* Note that if the code falls back on using TelephonyManager.getDeviceId(), the resulting ID will NOT
* change after a factory reset. Something to be aware of.
*
* Works around a bug in Android 2.2 for many devices when using ANDROID_ID directly.
*
* #see http://code.google.com/p/android/issues/detail?id=10603
*
* #return a UUID that may be used to uniquely identify your device for most purposes.
*/
public UUID getDeviceUuid() {
return uuid;
}
Yes. It is a device hardware serial number and it is unique. So on api level 2.3 and above you can use android.os.Build.ANDROID_ID to get it. For below 2.3 API level use TelephonyManager.getDeviceID().
you can read this http://android-developers.blogspot.in/2011/03/identifying-app-installations.html
Related
Background
I know that we can get the network usage (total bandwidth used of mobile&Wifi so far, from some specific time) of a specified app by using something like that (asked in the past, here) :
private final static int[] NETWORKS_TYPES = new int[]{ConnectivityManager.TYPE_WIFI, ConnectivityManager.TYPE_MOBILE};
long rxBytes=0L, txBytes=0L;
final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final String subscriberId = telephonyManager.getSubscriberId();
final ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(packageName, 0);
final int uid = applicationInfo.uid;
for (int networkType : NETWORKS_TYPES) {
final NetworkStats networkStats = networkStatsManager.queryDetailsForUid(networkType, subscriberId, 0, System.currentTimeMillis(), uid);
final Bucket bucketOut = new Bucket();
while (true) {
networkStats.getNextBucket(bucketOut);
final long rxBytes = bucketOut.getRxBytes();
if (rxBytes >= 0)
totalRx += rxBytes;
final long txBytes = bucketOut.getTxBytes();
if (txBytes >= 0)
totalTx += txBytes;
if (!networkStats.hasNextBucket())
break;
}
}
}
Docs:
https://developer.android.com/reference/android/app/usage/NetworkStatsManager.html#queryDetailsForUid(int,%20java.lang.String,%20long,%20long,%20int)
It's also possible to get the global network usage (using TrafficStats.getUidRxBytes(applicationInfo.uid) and TrafficStats.getUidTxBytes(applicationInfo.uid) ), but that's not what this thread is all about.
The problem
It seems Android Q is planned to cause a lot of device-identity functions to stop working anymore, and getSubscriberId is one of them.
What I've tried
I tried to set the targetSdk to 29 (Q) and see what happens when I try to use this.
As expected, I got an exception that shows me that I can't do it anymore. It says :
019-06-11 02:08:01.871 13558-13558/com.android.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.android.myapplication, PID: 13558
java.lang.SecurityException: getSubscriberId: The user 10872 does not meet the requirements to access device identifiers.
at android.os.Parcel.createException(Parcel.java:2069)
at android.os.Parcel.readException(Parcel.java:2037)
at android.os.Parcel.readException(Parcel.java:1986)
at com.android.internal.telephony.IPhoneSubInfo$Stub$Proxy.getSubscriberIdForSubscriber(IPhoneSubInfo.java:984)
at android.telephony.TelephonyManager.getSubscriberId(TelephonyManager.java:3498)
at android.telephony.TelephonyManager.getSubscriberId(TelephonyManager.java:3473)
Searching the Internet and here, I don't see this mentioned, but I have found about similar issues, of getting IMEI and other identifiers:
I am getting IMEI null in Android Q?
https://issuetracker.google.com/issues/130202003
https://issuetracker.google.com/issues/129583175
https://developer.android.com/preview/privacy/data-identifiers#device-ids
So for now I just made a bug report about it here (including a sample project) :
https://issuetracker.google.com/issues/134919382
The question
Is it possible to get network usage of a specified app on Android Q (when targeting to it) ? Maybe without subscriberId?
If so, how?
If not, is it possible by having root, or via adb?
EDIT:
OK, I don't know how to officially use this, but at least for root access it is possible to get the subscriberId, using this solution, found from here.
Meaning something like that:
#SuppressLint("MissingPermission", "HardwareIds")
fun getSubscriberId(telephonyManager: TelephonyManager): String? {
try {
return telephonyManager.subscriberId
} catch (e: Exception) {
}
val commandResult = Root.runCommands("service call iphonesubinfo 1 | grep -o \"[0-9a-f]\\{8\\} \" | tail -n+3 | while read a; do echo -n \"\\u\${a:4:4}\\u\${a:0:4}\"; done")
val subscriberId = commandResult?.getOrNull(0)
return if (subscriberId.isNullOrBlank()) null else subscriberId
}
It's not an official solution, of course, but it's better than nothing...
EDIT: the part of getting it via root is wrong. It doesn't help in any way.
You can provide null value for API 29 and above. It returns values for both WIFI and Mobile Data.
Documentation:
If applicable, the subscriber id of the network interface.
Starting with API level 29, the subscriberId is guarded by additional restrictions. Calling apps that do not meet the new requirements to access the subscriberId can provide a null value when querying for the mobile network type to receive usage for all mobile networks. For additional details see TelephonyManager.getSubscriberId().
Permissions (Don't forget to get permission from the user):
<uses-permission android:name="android.permission.READ_PHONE_STATE"
android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />
Example code:
//network stats
NetworkStatsManager networkStatsManager = (NetworkStatsManager) activity.getSystemService(Context.NETWORK_STATS_SERVICE);
int[] networkTypes = new int[]{NetworkCapabilities.TRANSPORT_CELLULAR, NetworkCapabilities.TRANSPORT_WIFI};
String subscriberId;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
TelephonyManager telephonyManager = (TelephonyManager) activity.getSystemService(Context.TELEPHONY_SERVICE);
try {
subscriberId = telephonyManager.getSubscriberId(); //MissingPermission
} catch (SecurityException e) {
subscriberId = null;
}
} else {
subscriberId = null;
}
Get NetworkStats for an app:
long receivedBytes = 0;
long transmittedBytes = 0;
for (int networkType : networkTypes) {
NetworkStats networkStats;
try {
networkStats = networkStatsManager
.queryDetailsForUid(networkType,
subscriberId,
0,
System.currentTimeMillis(),
appUid);
} catch (SecurityException e) {
networkStats = null;
}
if(networkStats != null) {
NetworkStats.Bucket bucketOut = new NetworkStats.Bucket();
while (networkStats.hasNextBucket()) {
networkStats.getNextBucket(bucketOut);
long rxBytes = bucketOut.getRxBytes();
long txBytes = bucketOut.getTxBytes();
if (rxBytes >= 0) {
receivedBytes += rxBytes;
}
if (txBytes >= 0) {
transmittedBytes += txBytes;
}
}
networkStats.close();
}
}
The google team in the comment of the thread that you have mentioned has said:
" Status: Won't Fix (Intended Behavior)
This is Working As Intended. IMEI is a personal identifier and this is not given out to apps as a matter of policy. There is no workaround.". So I guess the methods in the class NetworkStatsManager which require IMSI (which is also considered as a personal identifier) to work (like the queryDetailsForUid(int, String, long, long, int)) are now broken in Android Q. You may use those methods to get Wifi usage details of the apps (by passing empty string for subscriberId) but for getting Mobile usage details, you now have to rely on the good old TrafficStats class until the issue gets noticed and fixed.
We are using NetworkStatsManager.querySummaryForDevice(). Due to a serendipitous bug, we were passing null as the subscriberId for MOBILE in Q. It appears to be working on our devices. I'm not sure if this is a bug or a feature, but the values match our expected cellular usage.
All the said, we could just use TrafficStats for this use case, but it's erratic before Pie.
I have logged Issue 78084 with Google regarding the setMobileDataEnabled() method being no longer callable via reflection. It was callable since Android 2.1 (API 7) to Android 4.4 (API 19) via reflection, but as of Android L and later, even with root, the setMobileDataEnabled() method is not callable.
The official response is that the issue is "Closed" and the status set to "WorkingAsIntended". Google's simple explanation is:
Private APIs are private because they are not stable and might disappear without notice.
Yes, Google, we are aware of the risk of using reflection to call hidden method- even before Android came on the scene- but you need to provide a more solid answer as to alternatives, if any, for accomplishing the same result as setMobileDataEnabled(). (If you are displeased with Google's decision as I am, then log into Issue 78084 and star it as many as possible to let Google know the error of their way.)
So, my question to you is: Are we at a dead end when it comes to programmatically enable or disable mobile network function on an Android device? This heavy-handed approach from Google somehow does not sit well with me. If you have workaround for Android 5.0 (Lollipop) and beyond, I would love to hear your answer/discussion in this thread.
I have used the code below to see if the setMobileDataEnabled() method is available:
final Class<?> conmanClass = Class.forName(context.getSystemService(Context.CONNECTIVITY_SERVICE).getClass().getName());
final Field iConnectivityManagerField = conmanClass.getDeclaredField("mService");
iConnectivityManagerField.setAccessible(true);
final Object iConnectivityManager = iConnectivityManagerField.get(context.getSystemService(Context.CONNECTIVITY_SERVICE));
final Class<?> iConnectivityManagerClass = Class.forName(iConnectivityManager.getClass().getName());
final Method[] methods = iConnectivityManagerClass.getDeclaredMethods();
for (final Method method : methods) {
if (method.toGenericString().contains("set")) {
Log.i("TESTING", "Method: " + method.getName());
}
}
But it's not.
UPDATE: Currently, it's possible to toggle mobile network if the device is rooted. However, for non-rooted devices, it's still an investigative process as there is no universal method to toggle mobile network.
To extend Muzikant's Solution #2, can someone please try the solution below on an Android 5.0 rooted device (as I currently do not possess one) and let me know if it works or does not work.
To enable or disable mobile data, try:
// 1: Enable; 0: Disable
su -c settings put global mobile_data 1
su -c am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1
Note: The mobile_data variable can be found in Android API 21 source codes at /android-sdk/sources/android-21/android/provider/Settings.java and is declared as:
/**
* Whether mobile data connections are allowed by the user. See
* ConnectivityManager for more info.
* #hide
*/
public static final String MOBILE_DATA = "mobile_data";
While the android.intent.action.ANY_DATA_STATE Intent can be found in Android API 21 source codes at /android-sdk/sources/android-21/com/android/internal/telephony/TelephonyIntents.java and is declared as:
/**
* Broadcast Action: The data connection state has changed for any one of the
* phone's mobile data connections (eg, default, MMS or GPS specific connection).
*
* <p class="note">
* Requires the READ_PHONE_STATE permission.
* <p class="note">This is a protected intent that can only be sent by the system.
*
*/
public static final String ACTION_ANY_DATA_CONNECTION_STATE_CHANGED
= "android.intent.action.ANY_DATA_STATE";
UPDATE 1: If you don't want to implement the above Java codes in your Android application, then you can run the su commands via a shell (Linux) or command prompt (Windows) as follow:
adb shell "su -c 'settings put global mobile_data 1; am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1'"
Note: adb is located at /android-sdk/platform-tools/ directory. The settings command is only supported on Android 4.2 or later. Older Android version will report a "sh: settings: not found" error.
UPDATE 2: Another way to toggle mobile network on a rooted Android 5+ device would be to use the undocumented service shell command. The following command can be executed via ADB to toggle mobile network:
// 1: Enable; 0: Disable
adb shell "su -c 'service call phone 83 i32 1'"
Or just:
// 1: Enable; 0: Disable
adb shell service call phone 83 i32 1
Note 1: The transaction code 83 used in the service call phone command might change between Android versions. Please check com.android.internal.telephony.ITelephony for the value of the TRANSACTION_setDataEnabled field for your version of Android. Also, instead of hardcoding 83, you would be better off using Reflection to get the value of the TRANSACTION_setDataEnabled field. This way, it will work across all mobile brands running Android 5+ (If you don't know how to use Reflection to get the value of the TRANSACTION_setDataEnabled field, see solution from PhongLe below- save me from duplicating it here.) Important: Please note that transaction code TRANSACTION_setDataEnabled has only been introduced in Android 5.0 and later versions. Running this transaction code on earlier versions of Android will do nothing as the transaction code TRANSACTION_setDataEnabled does not exist.
Note 2: adb is located at /android-sdk/platform-tools/ directory. If you do not wish to use ADB, execute the method via su in your app.
Note 3: See UPDATE 3 below.
UPDATE 3: Many Android developers have emailed me questions regarding switching mobile network on/off for Android 5+, but instead of answering individual emails, I'll post my answer here so everyone can use it and adapt it for their Android apps.
First thing first, let's clear up some misconception and misunderstanding regarding:
svc data enable
svc data disable
The above methods would only turn background data on/off, not the subscription service, so the battery will drain a fair bit since the subscription service- an Android system service- will still be running in the background. For Android devices supporting multiple sim cards, this scenario is worse as the subscription service constantly scans for available mobile network(s) to use with the active SIM cards available in the Android device. Use this method at your own risk.
Now, the proper way to switch off mobile network, including its corresponding subscription service via the SubscriptionManager class introduced in API 22, is:
public static void setMobileNetworkfromLollipop(Context context) throws Exception {
String command = null;
int state = 0;
try {
// Get the current state of the mobile network.
state = isMobileDataEnabledFromLollipop(context) ? 0 : 1;
// Get the value of the "TRANSACTION_setDataEnabled" field.
String transactionCode = getTransactionCode(context);
// Android 5.1+ (API 22) and later.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
SubscriptionManager mSubscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
// Loop through the subscription list i.e. SIM list.
for (int i = 0; i < mSubscriptionManager.getActiveSubscriptionInfoCountMax(); i++) {
if (transactionCode != null && transactionCode.length() > 0) {
// Get the active subscription ID for a given SIM card.
int subscriptionId = mSubscriptionManager.getActiveSubscriptionInfoList().get(i).getSubscriptionId();
// Execute the command via `su` to turn off
// mobile network for a subscription service.
command = "service call phone " + transactionCode + " i32 " + subscriptionId + " i32 " + state;
executeCommandViaSu(context, "-c", command);
}
}
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
// Android 5.0 (API 21) only.
if (transactionCode != null && transactionCode.length() > 0) {
// Execute the command via `su` to turn off mobile network.
command = "service call phone " + transactionCode + " i32 " + state;
executeCommandViaSu(context, "-c", command);
}
}
} catch(Exception e) {
// Oops! Something went wrong, so we throw the exception here.
throw e;
}
}
To check if the mobile network is enabled or not:
private static boolean isMobileDataEnabledFromLollipop(Context context) {
boolean state = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
state = Settings.Global.getInt(context.getContentResolver(), "mobile_data", 0) == 1;
}
return state;
}
To get the value of the TRANSACTION_setDataEnabled field (borrowed from PhongLe's solution below):
private static String getTransactionCode(Context context) throws Exception {
try {
final TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final Class<?> mTelephonyClass = Class.forName(mTelephonyManager.getClass().getName());
final Method mTelephonyMethod = mTelephonyClass.getDeclaredMethod("getITelephony");
mTelephonyMethod.setAccessible(true);
final Object mTelephonyStub = mTelephonyMethod.invoke(mTelephonyManager);
final Class<?> mTelephonyStubClass = Class.forName(mTelephonyStub.getClass().getName());
final Class<?> mClass = mTelephonyStubClass.getDeclaringClass();
final Field field = mClass.getDeclaredField("TRANSACTION_setDataEnabled");
field.setAccessible(true);
return String.valueOf(field.getInt(null));
} catch (Exception e) {
// The "TRANSACTION_setDataEnabled" field is not available,
// or named differently in the current API level, so we throw
// an exception and inform users that the method is not available.
throw e;
}
}
To execute command via su:
private static void executeCommandViaSu(Context context, String option, String command) {
boolean success = false;
String su = "su";
for (int i=0; i < 3; i++) {
// Default "su" command executed successfully, then quit.
if (success) {
break;
}
// Else, execute other "su" commands.
if (i == 1) {
su = "/system/xbin/su";
} else if (i == 2) {
su = "/system/bin/su";
}
try {
// Execute command as "su".
Runtime.getRuntime().exec(new String[]{su, option, command});
} catch (IOException e) {
success = false;
// Oops! Cannot execute `su` for some reason.
// Log error here.
} finally {
success = true;
}
}
}
Hope this update clears up any misconception, misunderstanding, or question you may have about switching mobile network on/off on rooted Android 5+ devices.
Just to share a few more insights and possible solution (for rooted devices and system apps).
Solution #1
It seems like the setMobileDataEnabled method no longer exists in ConnectivityManager and this functionality was moved to TelephonyManager with two methods getDataEnabled and setDataEnabled.
I tried calling these methods with reflection as you can see in the code below:
public void setMobileDataState(boolean mobileDataEnabled)
{
try
{
TelephonyManager telephonyService = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
Method setMobileDataEnabledMethod = telephonyService.getClass().getDeclaredMethod("setDataEnabled", boolean.class);
if (null != setMobileDataEnabledMethod)
{
setMobileDataEnabledMethod.invoke(telephonyService, mobileDataEnabled);
}
}
catch (Exception ex)
{
Log.e(TAG, "Error setting mobile data state", ex);
}
}
public boolean getMobileDataState()
{
try
{
TelephonyManager telephonyService = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
Method getMobileDataEnabledMethod = telephonyService.getClass().getDeclaredMethod("getDataEnabled");
if (null != getMobileDataEnabledMethod)
{
boolean mobileDataEnabled = (Boolean) getMobileDataEnabledMethod.invoke(telephonyService);
return mobileDataEnabled;
}
}
catch (Exception ex)
{
Log.e(TAG, "Error getting mobile data state", ex);
}
return false;
}
When executing the code you get a SecurityException stating that Neither user 10089 nor current process has android.permission.MODIFY_PHONE_STATE.
So, yes this is an intended change to the internal API and is no longer available to apps that used that hack in previous versions.
(start rant: that dreadful android.permission.MODIFY_PHONE_STATE permission... end rant).
The good news are that in case you are building an app that can acquire the MODIFY_PHONE_STATE permission (only system apps can use that), you can use the above code to toggle mobile data state.
Solution #2
To check for current state of mobile data you can use the mobile_data field of Settings.Global (not documented in official documentation).
Settings.Global.getInt(contentResolver, "mobile_data");
And to enable/disable mobile data you can use shell commands on rooted devices (Just basic testing performed so any feedback in comments is appreciated).
You can run the following command(s) as root (1=enable, 0=disable):
settings put global mobile_data 1
settings put global mobile_data 0
I noticed that the service call method posted by ChuongPham does not work consistently on all devices.
I have found the following solution which, I think, will work without any issue on all ROOTED devices.
Execute the following via su
To enable mobile data
svc data enable
To disable mobile data
svc data disable
I think this is the simplest and best method.
Edit:
2 downvotes were for what I believe to be commercial reasons. The person has deleted his comment now. Try it yourself, it works!
Also confirmed to work by guys in comments.
I don't have enough reputation to comment but I have tried all the answers and found the following:
ChuongPham: Instead of using 83, I used reflection to get the value of the variable TRANSACTION_setDataEnabled from the com.android.internal.telephony.ITelephony so it works across all Android 5+ devices, regardless of brands.
Muzikant: Work if the app is moved to /system/priv-app/ directory (thanks to rgruet.) Else, it works via root, too! You just need to inform your users that the app will need a reboot before the changes to the mobile network will take place.
AJ: Work- sort of. Does not turn off subscription service so the devices I tested drained their batteries a fair bit. AJ's solution is NOT equivalent to Muzikant's solution despite the claim. I can confirm this by debugging different Samsung, Sony, and LG stock ROMs (I'm thorough) and can disprove AJ's claim that his solution is the same as Muzikant's. (Note: I can't get my hands on some Nexus and Motorola ROMs so haven't tested these ROMs with the proposed solutions.)
Anyway, hope it clears up any doubt over the solutions.
Happy coding!
PL, Germany
UPDATE: For those wondering how to get the value of the TRANSACTION_setDataEnabled field via reflection, you can do the following:
private static String getTransactionCodeFromApi20(Context context) throws Exception {
try {
final TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final Class<?> mTelephonyClass = Class.forName(mTelephonyManager.getClass().getName());
final Method mTelephonyMethod = mTelephonyClass.getDeclaredMethod("getITelephony");
mTelephonyMethod.setAccessible(true);
final Object mTelephonyStub = mTelephonyMethod.invoke(mTelephonyManager);
final Class<?> mTelephonyStubClass = Class.forName(mTelephonyStub.getClass().getName());
final Class<?> mClass = mTelephonyStubClass.getDeclaringClass();
final Field field = mClass.getDeclaredField("TRANSACTION_setDataEnabled");
field.setAccessible(true);
return String.valueOf(field.getInt(null));
} catch (Exception e) {
// The "TRANSACTION_setDataEnabled" field is not available,
// or named differently in the current API level, so we throw
// an exception and inform users that the method is not available.
throw e;
}
}
I found that su -c 'service call phone 83 i32 1' solution is most reliable for rooted devices. Thanks to Phong Le reference I have improved it by getting vendor/os specific transaction code using reflection. Maybe it will be useful for someone else. So, here is source code:
public void changeConnection(boolean enable) {
try{
StringBuilder command = new StringBuilder();
command.append("su -c ");
command.append("service call phone ");
command.append(getTransactionCode() + " ");
if (Build.VERSION.SDK_INT >= 22) {
SubscriptionManager manager = SubscriptionManager.from(context);
int id = 0;
if (manager.getActiveSubscriptionInfoCount() > 0)
id = manager.getActiveSubscriptionInfoList().get(0).getSubscriptionId();
command.append("i32 ");
command.append(String.valueOf(id) + " ");
}
command.append("i32 ");
command.append(enable?"1":"0");
command.append("\n");
Runtime.getRuntime().exec(command.toString());
}catch(IOException e){
...
}
}
private String getTransactionCode() {
try {
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
Class telephonyManagerClass = Class.forName(telephonyManager.getClass().getName());
Method getITelephonyMethod = telephonyManagerClass.getDeclaredMethod("getITelephony");
getITelephonyMethod.setAccessible(true);
Object ITelephonyStub = getITelephonyMethod.invoke(telephonyManager);
Class ITelephonyClass = Class.forName(ITelephonyStub.getClass().getName());
Class stub = ITelephonyClass.getDeclaringClass();
Field field = stub.getDeclaredField("TRANSACTION_setDataEnabled");
field.setAccessible(true);
return String.valueOf(field.getInt(null));
} catch (Exception e) {
if (Build.VERSION.SDK_INT >= 22)
return "86";
else if (Build.VERSION.SDK_INT == 21)
return "83";
}
return "";
}
Update:
Some of my users report that they have problem with turning ON mobile network via this method (turning off works correct). Does anyone have solution?
Update2:
After some digging the Android 5.1 code I've found that they changed signature of transaction. Android 5.1 brings official support of multi-SIM. So, the transaction need so-called Subscription Id as first parameter (read more here). The result of this situation is that the command su -c 'service call phone 83 i32 1' doesn't turn on Mobile Net on Android 5.1. So, the full command on Android 5.1 should be like this su -c 'service call phone 83 i32 0 i32 1' (the i32 0 is the subId, the i32 1 is command 0 - off and 1 - on). I've update the code above with this fix.
Solution #1 from Muzikant seems to work if you make the app "system" by moving the .apk to the /system/priv-app/ folder, not to the /system/app/ one (#jaumard: maybe that's why your test didn't work).
When the .apk is in the /system/priv-app/ folder, it can successfully request the dreadful android.permission.MODIFY_PHONE_STATE permission in the Manifest and call TelephonyManager.setDataEnabled and TelephonyManager.getDataEnabled.
At least that works on Nexus 5/ Android 5.0. The .apk perms are 0144. You need to reboot the device for the change to be taken into account, maybe this could be avoided - see this thread.
I derived final code from #ChuongPham and #A.J. for enable and disable cellular data. for enable you can call setMobileDataEnabled(true); and for disable you can call setMobileDataEnabled(false);
public void setMobileDataEnabled(boolean enableOrDisable) throws Exception {
String command = null;
if (enableOrDisable) {
command = "svc data enable";
} else {
command = "svc data disable";
}
executeCommandViaSu(mContext, "-c", command);
}
private static void executeCommandViaSu(Context context, String option, String command) {
boolean success = false;
String su = "su";
for (int i = 0; i < 3; i++) {
// Default "su" command executed successfully, then quit.
if (success) {
break;
}
// Else, execute other "su" commands.
if (i == 1) {
su = "/system/xbin/su";
} else if (i == 2) {
su = "/system/bin/su";
}
try {
// Execute command as "su".
Runtime.getRuntime().exec(new String[]{su, option, command});
} catch (IOException e) {
success = false;
// Oops! Cannot execute `su` for some reason.
// Log error here.
} finally {
success = true;
}
}
}
Not all phones and versions of android have things Enable/disable mobile data the same.
otherwise, this solution is tested on my phone (SAMSUNG SM-J100H)
To enable mobile data :
adb shell service call phone 27
To disable mobile data :
adb shell service call phone 28
To correct Muzikant Solution #2
settings put global mobile_data 1
Does enable only the toggle for mobile data but does nothing to the connectivity. Only the toggle is enabled. In order to get the data working using
su -c am broadcast -a android.intent.action.ANY_DATA_STATE --ez state 1
Gives error as the extra for
android.intent.action.ANY_DATA_STATE
Requires String Object while --ez parameter is used for boolean. Ref: PhoneGlobals.java & PhoneConstants.java. After using connecting or connected as extra using command
su -c am broadcast -a android.intent.action.ANY_DATA_STATE --es state connecting
Still doesnt do anything to enable the data.
The following solution works by enabling and disabling mobile data (as you would by clicking on the slider "Mobile data"). Requires root. Tested on LineageOS 16.0 (rooted):
Steps 1-3 are taken from the accepted answer at Turning off second SIM via adb shell/Tasker - using activities/intents:
Download jadx from https://github.com/skylot/jadx
ADB pull the devices framework.jar (adb pull /system/framework/framework.jar)
Open the .jar file with 7-Zip and extract the *.dex files. Open each .dex file with jadx-gui until you find the one with the following tree: com.android.internal.telephony.ITelephony
Find TRANSACTION_enableDataConnectivity and TRANSACTION_disableDataConnectivity, for me these are 38 and 39 respectively
From a root shell (e.g., adb shell or Termux), run service call phone 38 to enable data, and service call phone 39 to disable data.
When talking about unique Android ID's, I'm sure everyone has seen this, however I too am trying to come up with a solution to uniquely identify any android device. I would be happy to use a publicly released class but I am yet to find one.
In my situation, the requirement is to be able to uniquely identify any devices that have some form of an internet connection (such as GPRS or Wi-Fi) and run on API level 8 (v2.2 Froyo). I'm not aware of any devices that doesn't have Wi-Fi or a SIM so let me know if there is any!
We solved this problem in iOS by using a hash of the Wi-Fi mac address as all iOS devices should have this. However for Android, techniques (such as the answers to the SO post referenced above) refer to the TelephonyManager class which can return null (such as when the device doesn't have a SIM connection, like with the Nexus 7) or getContext().getContentResolver(), Secure.ANDROID_ID which according to here isn't 100% reliable on versions prior to Froyo (which is fortunate for me). It doesn't however state of any issues post-Froyo so again, if there are any, let me know! I've also read this can return null. According to here it can change on a factory reset, but that isn't a major concern.
So I put together this to generate a hopefully-unique-GUID:
[This method is not complete!]
public static String GetDeviceId(Context context)
{
// Custom String Hash 1
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("someRandomData"); // Not really needed, but means the stringBuilders value won't ever be null
// TM Device String
final TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
String tmDeviceId = tm.getDeviceId(); // Could well be set to null!
LogMsg.Tmp("TM Device String [" + tmDeviceId + "]");
// Custom String Hash 2
stringBuilder.append(tmDeviceId);
int customHash = stringBuilder.toString().hashCode();
LogMsg.Tmp("Custom String hash [" + customHash + "]");
// Device ID String
String androidIDString = android.provider.Settings.Secure.getString(context.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);
LogMsg.Tmp("Device ID String [" + androidIDString + "]");
// Combined hashes as GUID
UUID deviceUuid = new UUID(androidIDString.hashCode(), ((long)customHash << 32));
LogMsg.Tmp("Combined hashes as GUID [" + deviceUuid.toString() + "]");
return deviceUuid.toString();
}
So you may find tmDeviceId being set to null, in this case the the customHash will be the same regardless of the device but then this combined with androidIDString should be globally unique. I think. Obviously I will need to work out if tmDeviceId AND androidIDString aren't available and throw an exception there.
So... is this overkill? If so, is it safe to just use context.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID?
Why don't you get MAC address of the device as you've done in iOS? This can be performed in Android devices as well.
I'll give a code snippet that obtains mac address of the device..
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class HardwareUtil {
public static String getMacAddress()
{
try{
Process process = Runtime.getRuntime().exec("cat /sys/class/net/eth0/address");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
int read;
char[] buffer = new char[4096];
StringBuffer output = new StringBuffer();
while ((read = reader.read(buffer)) > 0){
output.append(buffer, 0, read);
}
reader.close();
process.waitFor();
String hwaddr = output.toString();
return hwaddr;
}catch (IOException e) {
e.printstacktrace();
}catch (InterruptedException e){
e.printstacktrace();
}
}
}
HardwareUtil.getMacAddress() will return mac address of the device.
EDIT: If mac address is not appropriate for your situation. Following can be useful!
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;
}
}
Don't forget to add following permission to your AndoridManifest.xml file if you use getDeviceId method.
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
What I want to do is to count 3G traffic and WiFi traffic respectively. Now I know how to do with WiFi. Below is the source code for WiFi. By this way I can count WiFi traffic for all the android phones of all manufactures. But I haven't found a similar way for 3g. Does anyone know?
//to get wifi interface
private static String getProp(String prop){
String output = "";
try{
Class<?> sp = Class.forName("android.os.SystemProperites");
Method get = sp.getMethod("get",String.class);
output = (String)get.invoke(null,prop);
}catch(Exception e){
e.printStackTrace();
}
return output;
}
//to get the traffic from system file
...
...
if (connectinTpe == ConnectivityManager.TYPE_WIFI){
String wifiInterface = getProp("wifi.interface");
if(wifiInterface == null || "".equals(wifiInterface)) wifiInterface = "eth0";
rxFile = "/sys/class/net/" +wifiInterface+ "/statistics/rx_bytes";
txFile = "/sys/class/net/" +wifiInterface+ "/statistics/tx_bytes";
}
...
...
Starting from API level 8 (Android 2.2) there is a class TrafficStats which provides what you need:
Class that provides network traffic statistics. These statistics
include bytes transmitted and received and network packets transmitted
and received, over all interfaces, over the mobile interface, and on a
per-UID basis.
On the older versions you can use the approach you mentioned (i.e. reading file content of /sys/class/net/... files). This blog post contains an excellent mapping between TrafficStats methods and file locations. And this SO post contains the source its author used to read those files values. According to it you should first try to read number from "/sys/class/net/rmnet0/statistics/rx_bytes" file (for "received bytes" value) and if it fails try "/sys/class/net/ppp0/statistics/rx_bytes" instead.
to get the current type of connection you can use the TelephonyManager: http://developer.android.com/reference/android/telephony/TelephonyManager.html
first check if the device is connected to the default mobile data connection and then check the connection type:
if (connectinTpe == ConnectivityManager.TYPE_MOBILE)
{
TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
int curConnectionType = tm.getNetworkType();
if(curConnectionType >= /*connection type you are looking for*/)
{
// do what you want
}
}
I'm looking for help to get the UUID of my Android phone. I have searched the net and found one potential solution but it is not working in the emulator.
Here is the code:
Class<?> c;
try {
c = Class.forName("android.os.SystemProperties");
Method get = c.getMethod("get", String.class);
serial = (String) get.invoke(c, "ro.serialno");
Log.d("ANDROID UUID",serial);
} catch (Exception e) {
e.printStackTrace();
}
Does anybody know why it isn't working, or have a better solution?
As Dave Webb mentions, the Android Developer Blog has an article that covers this. Their preferred solution is to track app installs rather than devices, and that will work well for most use cases. The blog post will show you the necessary code to make that work, and I recommend you check it out.
However, the blog post goes on to discuss solutions if you need a device identifier rather than an app installation identifier. I spoke with someone at Google to get some additional clarification on a few items in the event that you need to do so. Here's what I discovered about device identifiers that's NOT mentioned in the aforementioned blog post:
ANDROID_ID is the preferred device identifier. ANDROID_ID is perfectly reliable on versions of Android <=2.1 or >=2.3. Only 2.2 has the problems mentioned in the post.
Several devices by several manufacturers are affected by the ANDROID_ID bug in 2.2.
As far as I've been able to determine, all affected devices have the same ANDROID_ID, which is 9774d56d682e549c. Which is also the same device id reported by the emulator, btw.
Google believes that OEMs have patched the issue for many or most of their devices, but I was able to verify that as of the beginning of April 2011, at least, it's still quite easy to find devices that have the broken ANDROID_ID.
When a device has multiple users (available on certain devices running Android 4.2 or higher), each user appears as a completely separate device, so the ANDROID_ID value is unique to each user.
Based on Google's recommendations, I implemented a class that will generate a unique UUID for each device, using ANDROID_ID as the seed where appropriate, falling back on TelephonyManager.getDeviceId() as necessary, and if that fails, resorting to a randomly generated unique UUID that is persisted across app restarts (but not app re-installations).
Note that for devices that have to fallback on the device ID, the unique ID WILL persist across factory resets. This is something to be aware of. If you need to ensure that a factory reset will reset your unique ID, you may want to consider falling back directly to the random UUID instead of the device ID.
Again, this code is for a device ID, not an app installation ID. For most situations, an app installation ID is probably what you're looking for. But if you do need a device ID, then the following code will probably work for you.
import android.content.Context;
import android.content.SharedPreferences;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import java.io.UnsupportedEncodingException;
import java.util.UUID;
public class DeviceUuidFactory {
protected static final String PREFS_FILE = "device_id.xml";
protected static final String PREFS_DEVICE_ID = "device_id";
protected static UUID uuid;
public DeviceUuidFactory(Context context) {
if( uuid ==null ) {
synchronized (DeviceUuidFactory.class) {
if( uuid == null) {
final SharedPreferences prefs = context.getSharedPreferences( PREFS_FILE, 0);
final String id = prefs.getString(PREFS_DEVICE_ID, null );
if (id != null) {
// Use the ids previously computed and stored in the prefs file
uuid = UUID.fromString(id);
} else {
final String androidId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
// Use the Android ID unless it's broken, in which case fallback on deviceId,
// unless it's not available, then fallback on a random number which we store
// to a prefs file
try {
if (!"9774d56d682e549c".equals(androidId)) {
uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8"));
} else {
final String deviceId = ((TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE )).getDeviceId();
uuid = deviceId!=null ? UUID.nameUUIDFromBytes(deviceId.getBytes("utf8")) : UUID.randomUUID();
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
// Write the value out to the prefs file
prefs.edit().putString(PREFS_DEVICE_ID, uuid.toString() ).commit();
}
}
}
}
}
/**
* Returns a unique UUID for the current android device. As with all UUIDs, this unique ID is "very highly likely"
* to be unique across all Android devices. Much more so than ANDROID_ID is.
*
* The UUID is generated by using ANDROID_ID as the base key if appropriate, falling back on
* TelephonyManager.getDeviceID() if ANDROID_ID is known to be incorrect, and finally falling back
* on a random UUID that's persisted to SharedPreferences if getDeviceID() does not return a
* usable value.
*
* In some rare circumstances, this ID may change. In particular, if the device is factory reset a new device ID
* may be generated. In addition, if a user upgrades their phone from certain buggy implementations of Android 2.2
* to a newer, non-buggy version of Android, the device ID may change. Or, if a user uninstalls your app on
* a device that has neither a proper Android ID nor a Device ID, this ID may change on reinstallation.
*
* Note that if the code falls back on using TelephonyManager.getDeviceId(), the resulting ID will NOT
* change after a factory reset. Something to be aware of.
*
* Works around a bug in Android 2.2 for many devices when using ANDROID_ID directly.
*
* #see http://code.google.com/p/android/issues/detail?id=10603
*
* #return a UUID that may be used to uniquely identify your device for most purposes.
*/
public UUID getDeviceUuid() {
return uuid;
}
}
This works for me:
TelephonyManager tManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
String uuid = tManager.getDeviceId();
EDIT :
You also need android.permission.READ_PHONE_STATE set in your Manifest. Since Android M, you need to ask this permission at runtime.
See this anwser : https://stackoverflow.com/a/38782876/1339179
Instead of getting IMEI from TelephonyManager use ANDROID_ID.
Settings.Secure.ANDROID_ID
This works for each android device irrespective of having telephony.
String id = UUID.randomUUID().toString();
See Android Developer blog article for using UUID class to get uuid
<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
As of API 26, getDeviceId() is deprecated. If you need to get the IMEI of the device, use the following:
String deviceId = "";
if (Build.VERSION.SDK_INT >= 26) {
deviceId = getSystemService(TelephonyManager.class).getImei();
}else{
deviceId = getSystemService(TelephonyManager.class).getDeviceId();
}
Add
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
Method
String getUUID(){
TelephonyManager teleManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
String tmSerial = teleManager.getSimSerialNumber();
String tmDeviceId = teleManager.getDeviceId();
String androidId = android.provider.Settings.Secure.getString(getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);
if (tmSerial == null) tmSerial = "1";
if (tmDeviceId== null) tmDeviceId = "1";
if (androidId == null) androidId = "1";
UUID deviceUuid = new UUID(androidId.hashCode(), ((long)tmDeviceId.hashCode() << 32) | tmSerial.hashCode());
String uniqueId = deviceUuid.toString();
return uniqueId;
}