I am writing an Android application which detects DLNA and Chrome devices. Application design language does not support putting cast button on ActionBar. Instead I need to poll the DLNA and Chrome devices and display in a list.
The code written for detecting cast devices is as follows:
mMediaRouteSelector = new MediaRouteSelector.Builder()
.addControlCategory(CastMediaControlIntent.categoryForCast(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID))
.build();
mMediaRouterCallback = new MyMediaRouterCallback();
mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback,
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
Using the above code, I have trouble detecting the chrome devices. I takes ~10 minutes for the devices that are in the network to get detected. Sometimes it takes even more time. But using CastButton in the Actionbar, the devices are detected immediately.
How to detect the chrome devices without any latency?
I suggest you call mMediaRouter.getRoutes() and then rely on the callback to stay up-to-date as routes come and go. It may be the case that MediaRouter has already discovered your devices and depending on a number of factors, your callback may not receive a notification for such routes. If you see that in those cases mMediaRouter.getRoutes() has what you want, then you would need to filter the routes obtained by that call manually (using the same selector and RouteInfo.matchesSelector(selector).
Related
So my android app is behaving as a beacon, means, it will be advertising and other BLE devices will be connecting to it. Well, this is how our project is working so please don't raise questions on this as why am i using my app as a beacon and not as a scanner. Anyways, It behaves as a beacon and starts advertising and now I want to know that if a device connected to it. I cant find a way how to do this.
Of course, I am using this flutter package. beacon_broadcast 0.3.0
This is my code.
void startAdvertising() {
BeaconBroadcast beaconBroadcast = BeaconBroadcast();
beaconBroadcast
.setUUID(advertisingUUID)
.setMajorId(1)
.setMinorId(100)
.start();
}
First, Flutter is just a UI toolkit and has no support for other system APIs such as Bluetooth.
You should therefore look what the official Android APIs offer in the first place. Usually when using BluetoothLeAdvertiser for advertising, one often also adds an instance of BluetoothGattServer in order to handle connections. If you have created a BluetoothGattServer using openGattServer, you will get a onConnectionStateChange callback whenever a device connects or disconnects. So that answers your question how an Android app can get notified when a device connects. You probably also want to use the same API to add a GATT service so that the other device can communicate with your app. Other alternatives is to use the GATT client API if it's the other device that has a GATT server, or you might want to use the L2CAP CoC API.
Note that if Bluetooth is turned off/disabled/restarted, your BluetoothGattServer object will automatically die and you need to recreate it. To get notified when this happens, use a state change intent receiver for BluetoothAdapter.ACTION_STATE_CHANGED as explained in this example https://stackoverflow.com/a/9694138/556495 to recreate your BluetoothGattServer (and advertiser) when state is changed to STATE_ON.
Now, since you want to use Flutter but Flutter uses Dart, you cannot directly consume the Android APIs. Instead you need to write a bridge/plugin, to bridge your Dart code and Java code. See https://docs.flutter.dev/development/platform-integration/platform-channels for a tutorial how to do this. If you're lucky, someone else might have already created such a package that does exactly what you want. Unfortunately, the beacon_broadcast package you found, only implements BluetoothLeAdvertiser and not BluetoothGattServer, as can be seen by the source code here: https://github.com/pszklarska/beacon_broadcast/tree/master/android/src/main/kotlin/pl/pszklarska/beaconbroadcast.
In my application, I implemented the classic Bluetooth according to the official documentation: https://developer.android.com/guide/topics/connectivity/bluetooth.
By default startDiscovery() will scan for all the nearby bluetooth devices. But in many situations user/developer already knows the desired category of devices. In my case, you need to find a specific device that implements the method:
bluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord(SERVICE_NAME, MY_UUID_INSECURE);
Ideally, you need to implement the search and connect two devices in one click. How can I select the desired device from the list of found devices? This is for the convenience of using the application, so you do not have to choose from a variety of unnecessary devices. It will be a kind of filter like in BLE. But we are talking about Bluetooth Classic. How can I use SERVICE_NAME, MY_UUID_INSECURE, BluetoothClass to accomplish this task?
EDIT:
I found several solutions to this problem. But they're all not perfect. For now, I just exclude from the list of devices those that have a device.getType() == 2 (DEVICE_TYPE_LE).
Option 1
Search for a device that broadcasts the service with the same UUID as mine. To do this, use the fetchUuidsWithSdp() method for the found devices. Example implementation: https://stackoverflow.com/a/37070600
The disadvantage of this method. Time. You should wait until you receive BluetoothAdapter.ACTION_DISCOVERY_FINISHED before you make any calls to fetchUuidsWithSdp(). It will take 12-18 seconds. In addition to this one must wait for each subsequent call to fetchuuidsWithSdp() to complete, and then give a call to this method for another device. It will take about 3 seconds per device. In total, it can take a very long time to find the right device.
Option 2
Change the device name to a private key or a special name that the client can use to identify the device.
bluetoothAdapter.setName(name);
The main thing is not to forget to return the device name to the original.
saveName = bluetoothAdapter.getName();
Example implementation: https://stackoverflow.com/a/40138077/4716092
The disadvantage of this method. Changing the device name is not so easy in reality. If you do not return the old name of the device, the user may be upset.
I have an Asus P00A tablet (Android 7.0, API24) on which the BLE stops after some hours. (This affects any BLE app, not just my app using Android Beacon Library). Apps start working again if I manually switch off BLE then switch it back on.
The BluetoothMedic auto-fix system did not work for my tablet. It runs every 15 minutes but does not find a fault and so does not "power cycle" the Bluetooth. However, I hacked the BluetoothMedic class, adding this:
public void cycleBluetooth(Context context) {...}
and attached this to a button. I find this will restore BLE functionality. So I wondered what would happen if I unconditionally reset the BLE every 15 minutes. I added:
public static final int ALWAYS_RESET = 4;
and then call medic.enablePeriodicTests(context, BluetoothMedic.ALWAYS_RESET);
and add code inside BluetoothTestJob.onStartJob() which then calls BluetoothMedic.cycleBluetooth(). This behaves as expected and so far my app has run perfectly for 18 hours.
I am interested in any advice, such as:
1 Are there any tests other than the two in BluetoothMedic that I can run to detect that my tablet's Bluetooth has stopped? (I am happy to experiment).
2 Any comments on the hack I describe above? Should it be OK to unconditionally reset the Bluetooth every 15 minutes?
3 If the Bluetooth is reset ("power cycled") then is the rest of the Android Bluetooth Library OK with this? That is, will it carry on with monitoring and ranging that has been previously set up, or does the application code need to set take any action to get things going again? Note that this would apply to resets by the existing enablePowerCycleOnFailures() code as well as my ALWAYS_RESET hack above. (Maybe there are some crashes that could happen if the power cycling came at the wrong time?).
4 Could I suggest adding a callback so the application can learn if the Bluetooth has been cycled? Perhaps as a parameter to enablePowerCycleOnFailures()
5 I understand that background activities can be stopped by the OS, especially with Android 8. Would this also affect the regular 15 minute tests set up by enablePeriodicTests()?
The Android Beacon LIbrary's BluetoothMedic, as currently built, relies on the operating system's error code returned by a scan failure (or an advertising failure) to decide if the bluetooth stack is in a bad state warranting a power cycle.
For scans, if the onScanFailed callback is called with an error code of SCAN_FAILED_APPLICATION_REGISTRATION_FAILED which has the value of 2, the module considers it worthy of a power cycle..
For advertisements, if the onStartFailed callback is called with an error code of ADVERTISE_FAILED_INTERNAL_ERROR which has a value of 4, the module considers it worth of a power cycle..
These values were determined via experimentation, witnessing that on some devices, once an error callback is called with these values, bluetooth on the device would not work again without turning it off and back on. You can see the discussion of this in this thread.
You may want to see if there are other error codes on the Asus P00A that indicate a problem worthy of cycling bluetooth. To do this, wait for a failure, and see if attempts to start scanning call the onScanFailed callback with a distinct error code. If such error codes exist, this would be a better solution than cycling power to bluetooth regularly, as cycling power to bluetooth does break BLE GATT connections and the operation of bluetooth classic functions like speakers. The Android Beacon Library itself recovers from these power cycles just fine, although it will obviously not detect beacons until bluetooth is back on.
Because the BluetoothMedic uses the Android Job Scheduler for periodic tests, it is not affected by background limitations on Android 8+.
If you are interested in augmenting these functions in the library, please feel free to open an issue in the Github repo, and issue a Pull Request if you have code to share.
I am using pixel with latest android 8.1.0 update.
I am facing issue related to BLE advertisement scanning. Whenever I turned off the screen(i.e power button press) my scanning will stop.
it will restart immediately after turn on the screen.
I have checked latest code for BLE. google newly introduce this feature (Reference Link).
Is there any way to skip this part, I mean scan should not stop regardless of the screen on or off.
As of Android 8.1, unfiltered bluetooth scans are blocked when the screen is turned off. While it is surprising for such a dramatic change to be made in a minor release of Android, this is certainly an intended change based on the comments in the commit:
Stop unfiltered BLE scans when the screen goes off.
The workaround is to use a ScanFilter with all scans. The new 8.1 operating system code simply verifies that any scans active when the screen is off have at least one scan filter. If those conditions are met the scan results are delivered as in Android 8.0.x and earlier.
In order to set up such a scan, you must use the APIs introduced in Android 5.0 and create a ScanFilter with each scan. Below is a filter that will find manufacturer advertisements for any device from Apple with manufacturer ID 0x004c (this will include iBeacons):
ScanFilter.Builder builder = new ScanFilter.Builder();
builder.setManufacturerData(0x004c, new byte[] {});
ScanFilter filter = builder.build();
Similarly, if you are interested in GATT Service advertisements (like the kind used with Eddystone beacons) you can search for a GATT Service UUID with a filter like this:
ScanFilter.Builder builder = new ScanFilter.Builder();
String serviceUuidString = "0000feaa-0000-1000-8000-00805f9b34fb";
String serviceUuidMaskString = "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF";
ParcelUuid parcelUuid = ParcelUuid.fromString(serviceUuidString);
ParcelUuid parcelUuidMask = ParcelUuid.fromString(serviceUuidMaskString);
builder.setServiceUuid(parcelUuid, parcelUuidMask);
ScanFilter filter = builder.build();
If needed, you can add multiple filters to a single scan, and any that match will return results. The only real limitation here is that you must know all of the manufacturer codes or all of the GATT Service UUIDs that you might match up front, at least when scanning with the screen off.
You start your scan with code like this:
bluetoothAdapter.getBluetoothLeScanner().startScan(filters, settings, scanCallback);
EDIT: It is also possible to do this with an empty ScanFilter that looks like this:
ScanFilter.Builder builder = new ScanFilter.Builder();
ScanFilter filter = builder.build();
If you use such a scan filter, it will match any advertising packet, and still allow detections with the screen off on Android 8.1, effectively giving you the same behavior on Android 8.0.x and earlier.
EDIT 2: On the Galaxy Note 9 with Android 8.1 and perhaps other Samsung devices with 8.1, scans are blocked with the screen off even with an empty scan filter. Scans are allowed with the screen off with a non-empty scan filter as described above.
I faced the same issue. I had Scan filters in order to scan BLE devices even if the screen were locked. But on Samsung devices it didn't work, so I search on Samsung forum and I discovered Knox SDK (https://seap.samsung.com/sdk/knox-android).
And it was the solution of my problem. All you have to do is add it to your app, create a license and activate it and finally use this method addPackageToBatteryOptimizationWhiteList to unlock the scan when the Samsung device screen is lock.
Obviously not, unless they missed something. But it will still work in the background if you have scan filters, which you should have anyway. So is it really an issue?
in android 11
scanFilter can't being null
you need to set something then will working
like:
List<ScanFilter> filterList = new ArrayList<>();
filterList.add(new ScanFilter.Builder().setDeviceAddress(address).build());
BluetoothAdapter.getBluetoothLeScanner().startScan(filterList, scanSettings, scanCallback);
I am trying to get my Media Route Selector to show both MiraCast and Chromecast devices. I have a Chromecast receiver app and also make use of the Presentation API in Android so ideally the user should only have to click the media router button and chose the device they have without even having to think about it. I was following this guide (https://developer.android.com/guide/topics/media/mediarouter.html#selector) to achieve this and in the picture it shows both a Chromecast and wireless display available in the route selector. However, after adding the control categories I still only see the Chromecast.
Here is the relevant code.
mMediaRouteSelector = new MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.addControlCategory(CastMediaControlIntent.categoryForCast(CAST_APP_ID))
.build();
...
mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback,
MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
I have tried changing the callback flag also but saw no change.
EDIT:
I ended up creating a custom dialog factory for the router button and using a separate layout that includes a button that will send the user to wireless display settings. Not exactly the solution I was looking for but it appears that what I was actually trying to do is not supported.