I am developing an Application that has a BLE device that only shows up on scan when some parameters are sent on scanning.
The App nrf Connect do this task very well (When filter by raw data, and use the 0x02010612435542 raw data parameter).
The device won't show its name, neither it's UUID's, nor manufacturer data.
On nrf Connect, the only thing it shares is a raw return like this: 0X020106124355420000080390BECB49400400CB500CF (which is preciselly what I need right now).
And on scan by Mac Address (testing on one unit of the device), it only gets me its rssi)
My question is, just like nrfConnect, how can I code a filter or something like that, that will be sent as a parameter on scanning, so I can find my devices? The devices doesn't have names (it shows N/A on scan), and I can't add a list of Mac Address filters because there will be tons of devices of the same kind, when my application is finished.
private List<ScanFilter> scanFilters() {
List<ScanFilter> list = new ArrayList<ScanFilter>();
ScanFilter scanFilter = new ScanFilter.Builder().build();
list.add(scanFilter);
//What kind of filter do I use to send that data on scanning?
return list;
}
In your BLE scanner, you receive ScanResult containing an instance of BluetoothDevice i.e. the device scanned. At the very least, you should be able to read the BLE address of the device i.e. its hardware address. Use the method:
BluetoothDevice.getAddress();
This hardware address should have a predefined range, such as starting with "A3:B4" which enables filtering scanRecords. Ask your device manufacturer for how these hardware addresses are generated.
Also, if you know the manufacturer id of the devices you are supporting, you can set a filter based on manufacturer data:
ScanFilter scanFilter = new ScanFilter.Builder()
.setManufacturerData(manufacturerId, manufacturerData)
.build();
Note the first two bytes of the manufacturerData array is the manufacturerId.
EDIT: The manufacturerData are part of the bytes array returned by
scanResult.getScanRecord().getBytes()
The scanResult is passed to the ScanCallback you've given to BluetoothLESCanner.startScan(...)
I finally found the solution to my problem.
It wasn't the way I was thinking, but thanks to #matdev, I managed to figure it out somehow. And it does not answer the main question, i.e. if it is possible to send data to devices on scan, but, the solution works for me.
First, I made a filter by name, in which the device name == null.
private List<ScanFilter> scanFilters() {
List<ScanFilter> list = new ArrayList<ScanFilter>();
ScanFilter scanFilterName = new ScanFilter.Builder().setDeviceName(null).build();
list.add(scanFilterName);
return list;
}
It gave me a huge list with tons of devices. So I added another filter, but this time, on the scanResult(). This other filter is by MAC Address suffix, as pointed by #matdev, it is the same for my devices.
private Set<String> btDeviceData = new LinkedHashSet<String>();
private ScanCallback scanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice device = result.getDevice();
ScanRecord record = result.getScanRecord();
byte[] dataByteArray = record.getBytes();
if (device.getAddress().startsWith("F8:36:9B")) {
btDeviceData.add(device.getAddress());
}
}
};
By doing this, I got a list (Set to be specific) with only the devices I was looking for.
Related
Coming from an iOS dev background, when working with Bluetooth LE acting as a peripheral you can register for a callback when a "central" BLE device subscribes (enables notifications) for a characteristic.
I'm struggling to see how this is achieved on Android. If you're working with Bluetooth LE acting as the central, I can see how you'd subscribe:
bluetoothGatt.setCharacteristicNotification(characteristicToSubscribeTo, true);
Which is the same as this on iOS:
peripheralDevice.setNotifyValue(true, forCharacteristic characteristicToSubscribeTo)
Now, after calling the above on iOS, on the peripheral side you then get a callback to say the central has subscribed with a method similar to:
peripheralManager(manager, central subscribedCentral didSubscribeToCharacteristic characteristic) which then gives you a reference to the device that subscribed/enabled notifications and to what characteristic.
What's the equivalent on Android?
For clarity I'll point out that I don't need to subscribe to a characteristic on Android, the device is acting as the peripheral and needs to be notified when other devices subscribe to characteristics.
Sorry if this is obvious but I can't find it in the docs or elsewhere.
Just after setting the bounty I had an idea, haha should've waited a little more give my brain more time to think ;)
It seems that iOS has abstracted away a little bit how subscriptions work internally. Under the ios CoreBluetooth hood a certain "Descriptor" of the characteristic is written by the Center, which indicates the center wants to subscribe the characteristic's value.
Here is the code that you need to add to your BluetoothGattServerCallback subclass:
#Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
Log.i(TAG, "onDescriptorWriteRequest, uuid: " + descriptor.getUuid());
if (descriptor.getUuid().equals(Descriptor.CLIENT_CHARACTERISTIC_CONFIGURATION) && descriptor.getValue().equals(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
Log.i(TAG, "its a subscription!!!!");
for (int i = 0; i < 30; i++) {
descriptor.getCharacteristic().setValue(String.format("new value: %d", i));
mGattServer.notifyCharacteristicChanged(device, descriptor.getCharacteristic(), false);
}
}
}
would be best to use https://github.com/movisens/SmartGattLib for the uuid (Descriptor.CLIENT_CHARACTERISTIC_CONFIGURATION but the raw value is 00002902-0000-1000-8000-00805f9b34fb)
Agree with #stefreak
and,
bluetoothGatt.setCharacteristicNotification(characteristicToSubscribeTo, true);
did nothing for remote device, this API only changed a notification bit at the local Bluetooth stack, i.e. if peripheral send notifications to local, local stack will judge whether apps already registered this notification and if yes, transfer it to app otherwise, ignore it.
so except setCharacteristicNotification you should also need writeDescriptor for your registered notification(this is the steps that tell remote need send notifications).
I am working on an android app with an email feature. I want my users to be able to compose and send emails while in airplane mode. For that I need some sort of queue that can check if there is network and send, etc. I image this must have been done 100s of times. But I am not really sure why my searches aren't turning up much. Does anyone know of a library or git project that I can use to accomplish this? If not, does anyone know how to accomplish this?
I believe it is called the Queue and send pattern.
Update
I am starting a bounty on this question. What I hope for is a working example that does not use SMS. For my particular case I am working on an Appengine Connected Android Project. The client needs to send data (String, Bitmap, etc under a particular POJO say Dog) to the server. I want to be able to queue up these data somehow. I can use Gson to save data to file, etc. The bottom line is that I need to be able to check for network. When there is network I dequeue my queue into the server. If there is no network, I keep saving into the queue.
My queue can be Queue<Dog>, where Dog is my class with fields such as Bitmap (or path to image), String, long, etc.
I am looking for a working example. It can be very simple, but the example must work. A git zip would be great. I am giving up close to half of my points for this question.
class Dog{
String dogname;
String pathToImage;
int dogAge;
//etc.
}
//Design pattern for sending Dog to server
0) Unmarshall queue from file using Gson
1) Add dog to queue
2) If there is network, loop through queue and send data to server
3) if there is no network save queue to file
//Ideally, as soon as there is network, the method should be able to detect so and run to send data to server
First you need to set up a receiver to watch the wifi connection to see when they have data, you could also check for normal 3g/4g connections and make a broadcast receiver for that as well. todo this let use implement a broadcast receiver for connection status changes. put something like this in the manifest in the application tag
<receiver android:name=".NetworkChangeReceiver" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
now we need to make the receiver we just defined in the manifest
public class NetworkChangeReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
//here, check that the network connection is available. If yes, start your email service. If not, stop your email service.
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null) {
if (info.isConnected()) {
//start service
Intent intent = new Intent(this, ItemServiceManager.class);
startService(intent);
}
else {
//stop service
Intent intent = new Intent(this, ItemServiceManager.class);
stopService(intent);
}
}
}
}
What this does is puts a big fat antenna called NetworkChangeReceiver out in android land, that is fine tuned to listen in on when android has something to say about a change in the data connection status.
now you need to build your ItemServiceManager.class which should read from a database (it should also extend Service. It should choose the oldest item in the database, (email it, text it, upload to server, whatever), and if the connection was successful then remove the item from the database, and load the next oldest one. If there is no more then close the service and the broadcast receiver.
If you have a connection and the user needs to send more data, then add it to the database, and then make sure the service is started. Maybe notify it that it should double check the database (after a few seconds) before deciding it can close because nothing is there.
This is how you might disable your broadcast receiver.
PackageManager packageManager = context.getPackageManager();
ComponentName componentName = new ComponentName(context, NetworkChangeReceiver.class);
packageManager.setComponentEnabledSetting(componentName,PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
When a new item is to be uploaded, if there is no web connection, the email should be saved to the database and the broadcast receiver should be started to know when internet is back so it can know when to upload. You might start it up like this.
PackageManager packageManager = context.getPackageManager();
ComponentName componentName = new ComponentName(context, NetworkChangeReceiver.class);
packageManager.setComponentEnabledSetting(componentName,PackageManager.COMPONENT_ENABLED_STATE_ENABLED,PackageManager.DONT_KILL_APP);
The whole point is you only care about connection broadcasts when you have something stored to be uploaded but can not upload it because of no data connection. When you have nothing to upload, don't waste processing and battery by keeping your receiver/service around. And when you do have emails waiting, then start up you broadcastreceiver, to know when you have data connection so that you can start uploading.
I do not think anyone is going to write a whole working solution for you, hopefully this is more than enough to get you on your way.
Edit:
Another thing you can do, is let the server allow acceptance of an array of your items, that way you can just upload it all at once when you get a valid connection. Generally you would do this if each item was decently small. But if you are uploading pictures or videos or anything large, best to do it one at a time probably.
If I dial
*
#
*
#
8
2
5
5
#
*
#
*
, I get my android device id which starts with android-35c2acdd...
source
If I use Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID) the result starts with a96b4b27...
If I use ((TelephonyManager) Context.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId() the result starts with 3538330...
What is the difference between these ID-s? How can I get the result of the
*
#
*
#
8
2
5
5
#
*
#
*
dial?
IMEI
The IMEI is the 'MAC' for the telephony module - the unique ID that the telephone uses when it connects via GSM/GPRS/HSPDA/etc. The GSM network uses it to route calls and data from the phone over the GSM network right up to the gateway into the Internet (which is an IP network).
A telephony module is a chip or circuit board that handles the telephone network, either GSM or CMDA, and often has a slot for a removable SIM card. Some phones have more than one telephony module (active dual- or multi-SIM phones). Each telephony module has its own IMEI.
Manufacturers give each phone (strictly the telephony module) a unique IMEI during manufacturing. However the number can normally be rewritten if you have the right software. This is often done after a phone has been stolen to give the phone a new identity and bipass stolen phone blocking system.
The IMEI can be programmatically obtained using the TelephonyManager.getDeviceId() API.
CDMA phones have a ESN or MEID which are different lengths and formats, even though it is retrieved using the same API.
Android devices without telephony modules - for example many tablets and TV devices - do not have an IMEI. As Schlangi commented, some devices that do not have a telephony module fake the IMEI, so the presence of an IMEI does not (always) guarantee the device has a telephony module.
ANDROID_ID
The ANDROID_ID is another unique number on the phone - this is automatically generated by the OS as it boots for the first time (doing it this way makes it much easier for the manufacturers by removing a step from the production line).
The ANDROID_ID can (and does) change, for example:
Factory reset (including when reflashing the OS)
In software: eg https://play.google.com/store/apps/details?id=com.vcastroi.changeid
It is mainly used by developers (eg identifying and connecting to devices using adb)
ANDROID_ID can be used to identify an Android device given the caveats above, realistically meaning that it uniquely identifies the device over significant portions of the device lifetime, but cannot be relied on.
Also note that there was a bug in Froyo where many devices gave themselves the same ANDROID_ID. This is the bug
Other identifiers
There are a number of other things that can be used identify the device:
MAC address of the WiFi module: WifiManager.getConnectionInfo() -> WifiInfo.getMacAddress(). This can often be changed in software, but generally is constant over the device lifetime. Also it can only be read if the WiFi module is switched on.
MAC address of the BlueTooth module: BluetoothAdaptor.getAddress(). Like WiFi MAC, this can often be changed in software and may be off when you need it
The subscriber's telephone number. This may change if the user requests a new number from the telco, or if the user switches SIMs. It is obtained from TelephonyManager.getLine1Number(). This is only present for Android phone devices with a current SIM installed and a paid service with a telco.
The SIM contains its own identifying number (IMSI). This is obtained from the TelephonyManager.getSubscriberId() API. Obviously the SIM may not be present at any specific time, and it changes when the SIM is changed - and users can upgrade/replace their SIM while keeping the same number, so you can't say that this is one-to-one to a specific phone or user.
Related to the IMSI is the MSISDN. This functions as the identification of a subscription (your contract for a specific telephone number with your mobile provider) and therefore gives the device its telephone number. The MSISDN may be associated with several SIM cards, and therefore several phones. It comes with all the caveats for reading the SIM above. This may be retrieved with TelephonyManager.getSimSerialNumber(). Thanks Schlangi for the corrections and additions
Gingerbread and later has android.os.Build.SERIAL which many manufacturers set (but not all. Bugger).
Other notes
You need specific permissions to access each and every API, so if you try for all of them, your app's permissions in the Google Play store look fairly permissive.
I think this link explains all the other available options also https://android-developers.googleblog.com/2011/03/identifying-app-installations.html
Found on the web:
private static final Uri URI = Uri.parse("content://com.google.android.gsf.gservices");
private static final String ID_KEY = "android_id";
String getAndroidId(Context ctx) {
String[] params = { ID_KEY };
Cursor c = ctx.getContentResolver()
.query(URI, null, null, params, null);
if (!c.moveToFirst() || c.getColumnCount() < 2)
return null;
try {
return Long.toHexString(Long.parseLong(c.getString(1)));
} catch (NumberFormatException e) {
return null;
}
}
Add permission:
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
However, I doubt that is a documented ID and I would be carefull because that might not work if GTalk gets updated.
Source: http://blog.codepainters.com/2012/01/17/how-to-obtain-gtalk-android-id/
Also worth having a look at: http://www.toxicbakery.com/android-development/getting-google-auth-sub-tokens-in-your-android-applications/
There are some approach to get unique identifier on android phone.
Android ID
It is a 64-bit hex string which is generated on the device's first boot.
Generally it won't changed unless is factory reset.
Secure.getString(getContentResolver(), Secure.ANDROID_ID);
The Android ID , considered unreliable because it can sometimes be null.
The documentation states that it "can change upon factory reset".
This string can also be altered on a rooted phone.
String m_szAndroidID = Secure.getString(getContentResolver(), Secure.ANDROID_ID);
Returns: 9774d56d682e549c . No special permissions required.
2. The WLAN MAC Address string, is another unique identifier that you can use as a device id.
Before you read it, you will need to make sure that your project has the android.permission.ACCESS_WIFI_STATE
permission or the WLAN MAC Address will come up as null.
WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE);
String m_szWLANMAC = wm.getConnectionInfo().getMacAddress();
Returns: 00:11:22:33:44:55 (not a real address since this is a custom ROM , as you can see the MAC address can easily be faked).
WLAN doesn't have to be on, to read this value.
3. The BT MAC Address string, available on Android devices with Bluetooth, can be read if your project has the android.permission.BLUETOOTH permission.
BluetoothAdapter m_BluetoothAdapter = null; // Local Bluetooth adapter
m_BluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
String m_szBTMAC = m_BluetoothAdapter.getAddress();
Returns: 43:25:78:50:93:38 . BT doesn't have to be on, to read it.
4. IMEI only for Android devices with Phone use:
TelephonyManager TelephonyMgr = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);
String szImei = TelephonyMgr.getDeviceId(); // Requires READ_PHONE_STATE
This requires adding a permission in AndroidManifest.xml, and users will be notified upon installing
your software: android.permission.READ_PHONE_STATE. The IMEI is unique for your phone
and it looks like this: 359881030314356 (unless you have a pre-production device with an invalid IMEI like 0000000000000).
For more info refer this link.
IMEI
There is a mandatory requirement by the standardization bodies, that mobile devices for public networks may be uniquely identified by the IMEI number
It is the manufacturer's responsibility to set IMEI. In practice, developers sometimes see IMEIs like 000000... or 123456... Sometimes phones with identical IMEI go to production, which of course is a bug that should be fixed...
ANDROID_ID
A 64-bit number (as a hex string) that is randomly generated on the device's first boot and should remain constant for the lifetime of the device. (The value may change if a factory reset is performed on the device.)
It looks like Android does not trust the manufacturers and provides an alternative unique ID.
EDIT:
This is what I get (instead of IMEI) on and Android device that is not a phone:
$ adb shell dumpsys iphonesubinfo
Phone Subscriber Info:
Phone Type = GSM
Device ID = null
I think all the information provided above is well enough to understand the codes.
Yet I think you are still "not able to see the result of the ##8255## dial" (plz excuse if I went wrong somewhere in understanding this)
I assume the reason behind this is one of the latest bug fix against USSD code made in Android.
(you may read more about this and check if your device is on the list. its all over the web)
Finally, if you just want to get the android ID straightaway i suggest you to use this app-
https://play.google.com/store/apps/details?id=com.redphx.deviceid&hl=en
I want to just discover peripheral devices when i start my Bluetooth Device discovery, my app must not discover/show other devices. is this any how possible?
this is how i am searching for devices
IntentFilter filter = new IntentFilter("android.bluetooth.devicepicker.action.DEVICE_SELECTED");
registerReceiver(mBtPickReceiver,filter);
startActivity(new Intent("android.bluetooth.devicepicker.action.LAUNCH")
.putExtra("android.bluetooth.devicepicker.action.EXTRA_NEED_AUTH",false));
To reduce (filter) the number of searched devices as per your requirements, check the Class Type of your desired bluetooth device. Once you know the type , you may filter the searched bluetooth devices based on their class type. For example:
BluetoothDevice btd = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if(btd.getBluetoothClass().getDeviceClass() == BluetoothClass.Device.bla_bla_bla)
{
//do whatever you want with the filtered device
}
I'm writing a program that speaks with an external accessory over rfcomm.
My problem is that I don't know what the correct way of identifying my device is.
the way I do it now is like this:
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter
.getBondedDevices();
for (BluetoothDevice device : pairedDevices) {
if (device.getName().equals(MY_DEVICE_NAME)) {
this.myDevice = device;
break;
}
}
This method however relies on the name of the device which to me seems dirty and bad :)
is there a better way to do this?
I tried looking at all the methods of BluetoothDevice but none seemed to help - is the name really the best way to do it?
I saw that in some places people say that I should use UUIDs but that is used to open the socket to the device once I have it:
_socket = myDevice.createRfcommSocketToServiceRecord(MY_UUID);
is there a better way to do it?
Devices of the same kind/functionality and/or brand will usually have a similar name. For example, all RN-41 devices from Roving Networks have the following name:
FireFly-XXXX
where XXXX is the last 4 digits of the device's address. That means you can use the following to connect to any of them:
if (device.getName().startsWith("FireFly-")) {
this.myDevice = device;
break;
}
This is exactly what I do in my app and haven't found any more reliable/consistent way to do it. As a generalization, you should be able to use a regular pattern if the name in the devices you are interested in is any more complex than the example above.
You can use myDevice.getAddress() to get the bluetooth device address and compare, it will always be unique (unlike name)
You can also use BluetoothDevice.getBluetoothClass() for at narrowing down which devices might be relevant.
BluetoothClass.getMajorDeviceClass() will tell you roughly what kind of device it is - a phone, a computer, an audio or video device, or whatever.
BluetoothClass.hasService() further specifies some capabilities of the device.
Within each of the major classes, some minor classes are defined - what kind of computer / audio-video device / phone / health equipment etc. it is.
Also, on recent versions of the Android platform (API level 15+), you can query for the service records of a device, without having to connect to it. See BluetoothDevice.fetchUuidsWithSdp() and BluetoothDevice.getUuids().