android BluetoothDevice.getName() return null - android

On sometime, BluetoothDevice.getName() return null. How can i fix it?
remoteDeviceName maybe null in following code. And i need distinguish my device and other devices by remoteDeviceName.
BluetoothAdapter.getDefaultAdapter().startLeScan(new LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device, final int rssi,
byte[] scanRecord) {
String remoteDeviceName = device.getName();
Log.d("Scanning", "scan device " + remoteDeviceName);
});

Finally, i found out the solution:
1.For device connected:
Read device name from gatt characteristic org.bluetooth.characteristic.gap.device_name of service org.bluetooth.service.generic_access.
2.For device no connected:
/**
* Get device name from ble advertised data
*/
private LeScanCallback mScanCb = new LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device, final int rssi,
byte[] scanRecord) {
final BleAdvertisedData badata = BleUtil.parseAdertisedData(scanRecord);
String deviceName = device.getName();
if( deviceName == null ){
deviceName = badata.getName();
}
}
////////////////////// Helper Classes: BleUtil and BleAdvertisedData ///////////////
final public class BleUtil {
private final static String TAG=BleUtil.class.getSimpleName();
public static BleAdvertisedData parseAdertisedData(byte[] advertisedData) {
List<UUID> uuids = new ArrayList<UUID>();
String name = null;
if( advertisedData == null ){
return new BleAdvertisedData(uuids, name);
}
ByteBuffer buffer = ByteBuffer.wrap(advertisedData).order(ByteOrder.LITTLE_ENDIAN);
while (buffer.remaining() > 2) {
byte length = buffer.get();
if (length == 0) break;
byte type = buffer.get();
switch (type) {
case 0x02: // Partial list of 16-bit UUIDs
case 0x03: // Complete list of 16-bit UUIDs
while (length >= 2) {
uuids.add(UUID.fromString(String.format(
"%08x-0000-1000-8000-00805f9b34fb", buffer.getShort())));
length -= 2;
}
break;
case 0x06: // Partial list of 128-bit UUIDs
case 0x07: // Complete list of 128-bit UUIDs
while (length >= 16) {
long lsb = buffer.getLong();
long msb = buffer.getLong();
uuids.add(new UUID(msb, lsb));
length -= 16;
}
break;
case 0x09:
byte[] nameBytes = new byte[length-1];
buffer.get(nameBytes);
try {
name = new String(nameBytes, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
break;
default:
buffer.position(buffer.position() + length - 1);
break;
}
}
return new BleAdvertisedData(uuids, name);
}
}
public class BleAdvertisedData {
private List<UUID> mUuids;
private String mName;
public BleAdvertisedData(List<UUID> uuids, String name){
mUuids = uuids;
mName = name;
}
public List<UUID> getUuids(){
return mUuids;
}
public String getName(){
return mName;
}
}

BluetoothDevice.getName() may return null if the name could not be determined. This could be due to any number of factors. Regardless, the name is the friendly name of the device, and shouldn't be used to distinguish it from other devices. Instead, use the hardware address through getAddress().

I know this is old but this more spec-oriented answer may help answer some cases.
In Bluetooth Low Energy, advertisement and scan-response data is only required to have the Bluetooth Address. Advertisement data is how a client BTLE endpoint discovers a service device. A client can request a scan response and get more data. The device name is optional in this data. However, the BTLE spec requires that all Bluetooth Low Energy endpoints support the Generic Access service which is required to support the Device Name characteristic. Unfortunately, to read that characteristic the Android must first connect and do service discovery. If the advertisement/scan response does not provide the information, I do not believe Android connects to the device to get the name. At least I have never seen any indication of connecting without the app specifically requesting a connection. This is not what you want to be required to do if you want to make a decision to connect.
Fortunately, most BTLE devices I have worked with do provide their name in the advertisement or scan response.
Another possibility is that the device may place the name in the scan response part of the advertisement. Depending upon how one has set up Android's BTLE scanner, one might get only the advertisement data and not the scan response. In this case the name will not be found if the device puts it in the scan response. The default scanner settings, however, are such that a scan response must be received before the scan data is passed up to the app.

On Marshmallow, utilize ScanRecord.getDeviceName() to retrieve the local name embedded in the scan record.
BluetoothDevice.getName() is unreliable if the local name is included in a scan response, rather than in the immediate advertising packet.
#Override
public void onScanResult(int callbackType, ScanResult scanResult) {
super.onScanResult(callbackType, scanResult);
// Retrieve device name via ScanRecord.
String deviceName = scanResult.getScanRecord().getDeviceName();
}

I was trying to display name of my RN4020 Bluetooth module and faced the same issue. Found the problem in Microchip's forum:
If you enabled private service or MLDP, the maximum bytes of device
name is 6 bytes, due to the 31 byte advertisement payload limitation.
I had set the device name to 9 characters. Setting the name to 4 bytes fixed the issue.
If you recognize the UUID's of your custom services so you know its your device you can also connect to the device and read its name (if its longer than 6 bytes in my case). This was also suggested in Microchips forum.
http://www.microchip.com/forums/m846328.aspx

I've found that if you query for the device's name immediately after it's picked up at scanning it may return null. To get around this I poll a runnable every second or so on the UI thread a maximum of 3 times (So 3 seconds), and the name is usually resolved by then.
Note, in the snippet provided, the enclosing class implements Runnable, hence why I can pass this into View.postDelayed(Runnable action, long delayMillis)
private static final int MAX_NAME_CHECKS = 3;
private static final int NAME_CHECK_PERIOD = 1000;
int nameChecks;
#Override
public void run() {
resolveName();
}
/**
* Checks for the device name, for a maximum of {#link ViewHolder#MAX_NAME_CHECKS}
* as the name may not have been resolved at binding.
*/
private void resolveName() {
if (device != null) {
String name = device.getName();
boolean isEmptyName = TextUtils.isEmpty(name);
if (isEmptyName) deviceName.setText(R.string.unknown_device);
else deviceName.setText(name);
// Check later if device name is resolved
if (nameChecks++ < MAX_NAME_CHECKS && isEmptyName)
itemView.postDelayed(this, NAME_CHECK_PERIOD);
}
}

For someone hasn't found the solution.
Bluetooth advertisement package has maximum size is 31 bytes. So if your device has long name. It can be truncated.
See: https://devzone.nordicsemi.com/f/nordic-q-a/14/what-s-the-maximum-size-for-an-advertisement-package
If you do want to get correct name of bluetooth device (even long name), please don't use startLeScan(). Instead of it, using method startDiscovery().
Google said: "The discovery process usually involves an inquiry scan of about 12 seconds, followed by a page scan of each device found to retrieve its Bluetooth name.". I've tried and it works like a charm.
See: https://developer.android.com/guide/topics/connectivity/bluetooth

Related

Android bluetooth get the heart rate measurement

I want to get the value of the HRM of an "A&D UA-651BLE" device.
this is what's written in the datasheet of this device to get the HRM value:
Set the application to pairing mode to start scanning.
Start pairing of A&D BLE device following each instruction manual.
At pairing mode, the application should set time and date and any other device settings
to A&D BLE device. After successful pairing, A&D BLE device shows “End” on the screen.
Take a measurement and finish the measurement, then A&D BLE device start BLE
connection with advertising. The application starts scanning with suitable interval so that
the application catches the advertising of A&D BLE device as soon as it can.
At initial connection or pairing, the Application set “2” to CCCD (Client Characteristic
Configuration Descriptor) so that A&D BLE device sends a measurement data with
Indication.
After A&D device recognizes to be set “2” to CCCD and to be synchronized time and date
within 5 seconds after connected, send the data with Indication.
If the timeout set CCCD and time and date is expired, A&D BLE device will not send data
and store the data in memory. The stored data in A&D BLE device can send next
successful connection.
this is my service code:
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
// This is specific to Heart Rate Measurement.
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
}
and this is the method that read data:
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
for(byte byteChar : data)
stringBuilder.append(String.format("%02X ", byteChar));
Log.e("HRM value",stringBuilder.toString());
dataComposition.put(characteristic.getUuid().toString(),stringBuilder.toString());
intent.putExtra(EXTRA_DATA,dataComposition);
}
the problem is that this code doesn't return any data !!
There's an Android Open Source Project example that does precisely this, easiest option would be to clone the android-BluetoothLeGatt code, build and compare it to your own. If you can't spot the difference / issue simply deploy both app's and step through both sets of code. Having some known working code will also help to rule out the possibility that the HRM is not functioning properly.
Do you have and example , i try this with equal device and i cant obtain the information y try with
public String response() {
if (mConnected) {
mBluetoothLeService.readCharacteristic(characteristica);
byte response[] = characteristica.getValue();
String respuesta = ReadBytes(response);
mBluetoothLeService.disconnect();
return respuesta;
} else {
return null;
}
}

android device.getUuids returns null

I'm trying to connect to an Arduino Uno via an android app using Bluetooth Low Energy (BLE).
I'm developing on Android Studio, testing with a Samsung Galaxy S4, and with an Android version 5.0.1
I followed this link: http://www.truiton.com/2015/04/android-bluetooth-low-energy-ble-example/
I'm scanning devices and when I found one, I would like to get it's UUID before connecting to it, to make sure that it's the right type of device:
mScanCallback = new ScanCallback() {
#Override
#TargetApi(21)
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice btDevice = result.getDevice();
ParcelUuid[] uuids = btDevice.getUuids(); //<-- this is always null!! :(
Log.d(TAG, ""+btDevice.fetchUuidsWithSdp()); //<-- prints true.
Log.d(TAG, "result : " + result.toString()); //<-- prints a bunch of relevant info that contains the UUID I want to check.
Log.d(TAG, "uuids : " + uuids); //<-- prints null.
/*
for (ParcelUuid u : uuids) {
//Compare with the UUID of my device, and connect if ok.
}
*/
}
However, btDevice.getUuids(); is always returning null with no error...
How can I get the UUID of the scanned device?
A brute force method would be to use regexp with the result.toString() to grab what I want but there must be a better way isn't it?
Thanks
BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
// scan for devices
scanner.startScan(new ScanCallback() {
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public void onScanResult(int callbackType, ScanResult result)
{
List<ParcelUuid> uuids = result.getScanRecord().getServiceUuids();
}
}
this is worked for me in Android 6.0.1
After hours of searching I find out that getUuid() is not the way to go if you want to retrieve the uuid of an iBeacon.
If that is the case, you need to get it directly from the Scan result result object.
I managed to work by using the answer provided by ADEBAYO OSIPITAN.
BLE obtain uuid encoded in advertising packet
Here is his code snipet:
//For APIs greater than 21, Returns Device UUID
public String getUUID(ScanResult result){
String UUIDx = UUID
.nameUUIDFromBytes(result.getScanRecord().getBytes()).toString();
return UUIDx;
}
From Java DOC:
public ParcelUuid[] getUuids ()
Added in API level 15
Returns the supported features (UUIDs) of the remote device.
This method does not start a service discovery procedure to retrieve the UUIDs from the remote device. Instead, the local cached copy of the service UUIDs are returned.
Use fetchUuidsWithSdp() if fresh UUIDs are desired.
Requires BLUETOOTH.
Returns
the supported features (UUIDs) of the remote device, or null on error
Your ScanResult probably is a error

HM-10 Bluetooth Module - BLE 4.0 Keep Losing Connection

has anyone tried using HM-10 Bluetooth module?
I'm able to pair with it using an Android device and passing the pre-defined PIN. Based on the UART return, the pairing is successful (module returns OK+CONN - means a connection was established)
However, after a few seconds (2-3), the UART receives OK+LOST; means the connection was lost. Also, the LED starts blinking (normally, when a connection is active, it stays lit)
Is this normal behaviour for bluetooth in general or the HM-10 module.
This is the product's website: http://www.jnhuamao.cn/bluetooth.asp?ID=1
I'm not sure, but HM -10 don't support rfcom. It's mean that you must use GATT functionality for communication. Entity of BLE is usage of minimum data package as it possible, so BLE don't hold the connection all times and use something like statuses [attributes].
So, few code lines for example, how work with BLE:
1.
BluetoothAdapter mBluetoothAdapter = mBluetoothManager.getAdapter();
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(DEVICE_ADDR);
That's device initiation, the same like with simple bluetooth, where DEVICE_ADDR is the MAC of your BLE(how to find this address you can find in google or stack overflow, its trivial)
2.
BluetoothGattService mBluetoothGattService;
BluetoothGatt mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
mBluetoothGatt.discoverServices();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
List<BluetoothGattService> gattServices = mBluetoothGatt.getServices();
for(BluetoothGattService gattService : gattServices) {
if("0000ffe0-0000-1000-8000-00805f9b34fb".equals(gattService.getUuid().toString()))
{
mBluetoothGattService = gattService;
}
}
} else {
Log.d(TAG, "onServicesDiscovered received: " + status);
}
}
};
So, what this code mean: if u can see from this part of code, i describe how GATT service find. This service needed for "attribute" communication. gattService.getUuid() has few uuids for communication(4 in my module), some of them used for RX, some for TX etc. "0000ffe0-0000-1000-8000-00805f9b34fb" that is one of uuid that use for communication thats why i check it.
The final part of code is message sending:
BluetoothGattCharacteristic gattCharacteristic = mBluetoothGattService.getCharacteristic(UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"));
String msg = "HELLO BLE =)";
byte b = 0x00;
byte[] temp = msg.getBytes();
byte[] tx = new byte[temp.length + 1];
tx[0] = b;
for(int i = 0; i < temp.length; i++)
tx[i+1] = temp[i];
gattCharacteristic.setValue(tx);
mBluetoothGatt.writeCharacteristic(gattCharacteristic);
After sending message contain hold on and you can send another message or can close connection.
More info, you can find on https://developer.android.com/guide/topics/connectivity/bluetooth-le.html.
PS: MAC address of your module can find with ble scanner code or with AT cmd:
on my firmware AT+ADDR or AT+LADDR
About UUIDs usage: not sure, but in my case, i find it with next AT+UUID [Get/Set system SERVER_UUID] -> Response +UUID=0xFFE0, AT+CHAR [Get/Set system CHAR_UUID] - Response +CHAR=0xFFE1. Thats why i make conclusion that UUID which i must use fe "0000[ffe0/is 0xFFE0 from AT response]-0000-1000-8000-00805f9b34fb"

What is the purpose of the PowerProfile class in android

I was tasked with writing an app that gets details of how much power is being consumed by different apps. I was looking at the PowerProfile class and I am not sure what exactly it is used for. There is this method in the class
Returns the average current in mA consumed by the subsystem
Parameters:
type the subsystem type
Returns:
the average current in milliAmps.
public double getAveragePower(String type) {
if (sPowerMap.containsKey(type)) {
Object data = sPowerMap.get(type);
if (data instanceof Double[]) {
return ((Double[])data)[0];
} else {
return (Double) sPowerMap.get(type);
}
} else {
return 0;
}
}
If I test it by using it as follows:
String POWER_BLUETOOTH_ACTIVE = "bluetooth.active";
String POWER_PROFILE = "com.android.internal.os.PowerProfile";
try{
Constructor[] constructors = Class.forName(POWER_PROFILE).getConstructors();
Constructor c = constructors[0];
Class[] paramTypes = c.getParameterTypes();
Object params = (Object) this.getActivity().getBaseContext();
Object filledObject = c.newInstance(params);
Method batteryMeth1 = Class.forName(POWER_PROFILE).getMethod("getAveragePower", String.class);
Object barglist1[] = new Object[1];
barglist1[0] = new Object[1];
barglist1[0] = POWER_BLUETOOTH_ACTIVE;
double btlife = (Double) btMeth1.invoke(filledObject, barglist1);
Log.d("BatteryLog", "average BT active mAH " + btlife);
btInfo.setText("avg bt mAH is " + btlife);
}
catch (Exception e)
{
Log.d("BTLOg", "average BT active mAH " + btlife);
}
Then I always get 38.8 for bluetooth mAH used, even if I use my Bluetooth a bunch in between runs. Is this normal, and why would I always get the same mAH values?
The bluetooth is consuming power when pairing with other device or transmitting/receiving mode. Pairing mode is responsible to discover active bluetooth nearby then make a connection for the first time to get the MAC address & name of the bluetooth. It's like you are going to say hi to a beautiful stranger (pairing), it takes much courage ,no? then you can make a conversation (transmitting/receiving mode). Power consume in pairing mode is greater (more courage) than bluetooth transmittin/receiving mode whether there are data streamed or not. But for some bluetooth module embedded in your device there are some bluetooth supported sleep mode. The power draw in sleep mode is so little.
So in your case, maybe the bluetooth already in paired mode so it's consume the same amount. Try to pair the bluetooth or discovery bluetooth in Android it might consume more power

How to find serial number of Android device?

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

Categories

Resources