We are using RxJava in Android a lot, with good reasons. However, we still need to use code that is not built with RxJava, so let’s wrap them.
For very simple synchronous APIs, you can use Observable.just() to wrap them. e.g. you can use Observable.just(1, 2, 3, 4, 5) to emit an integer sequence from 1 to 5.
If the API is blocking, you can use Observable.defer() to wrap them:
And if you want RxJava to do try...catch for you, you can use Observable.fromCallable():
Usually, existing code uses callbacks to support asynchronous operations, e.g. in Android we can request location updates like this:
For advanced RxJava users, who don’t need to read this article, you can use Observable.create() to wrap it, and fullfil the contract by yourself. But for us, we can easily use Observable.fromEmitter() to handle the case, and let the framework to help us:
It’s extremely easy to share with Intent on Android. However, there are some apps that capture the ACTION_SEND intents, but doesn’t allow the app to pre-fill text set with EXTRA_TEXT, resulting in poor user experience.
With the following code, we can easily exclude some unwanted apps from the chooser intent:
The above code is taken from here. Enjoy and happy coding!
Osmosis is a command line application for processing Open Street Map data, with the souce code available here. The tool provides e.g. ability to generate and read data dumps, extract data inside a bounding box, etc. You can also easily write your own plugin to convert OSM data to e.g. formats your application understands.
Now, let’s start.
Write a Simple Plugin
An osmosis plugin is basically a normal jar, plus a plugin.xml file that describes how to load the plugin as shown below:
As we can see, the plugin is loaded by MyPluginLoader:
The following shows MyFactory that creates the real tasks:
Now, a simple task that does nothing but print some messages:
Just copy the generated jar to ~/.openstreetmap/osmosis/plugins folder.
Use the Plugin
You can now use your plugin: osmosis --read-pbf latest.osm.pbf --my-plugin, and you will get some output like below:
Yes, it’s that simple to create your own osmosis plugin, and time to hack now!
To scan e.g. heart rate monitors, you’re supposed to use code like this:
Here, the service UUID is to describe the service the peripheral devices provide, e.g. 0000180d-0000-1000-8000-00805f9b34fb is the service UUID for heart rate monitors.
Well, the reality is, the above two approaches may or may not work, depending on the specific device and OS version (e.g. my Samsung Galaxy S3 with Android 4.3 doesn’t support the first filter-based approach). It’s a known issue, and only fixed in Android 5.0 (only checked with Nexus 5, and seems working fine).
So, in your code, you have to scan devices with both of the ways, and hope that one shall work. With the second approach, you have to check if the scanned device is your target device through device names or even connect to it and figure out the services it provides.
Sometimes, you might need to involve the users. First, ask your users to turn off WiFi and try again. Not working? Turn off Bluetooth and on again and give a try. Still not working? OK, the Bluetooth service is crashed and can’t be restarted unless you reboot the device. So, just reboot your device like you often do with your Windows PC, then you shouldTM be fine.
If you’ve paired with BLE devices, there’s a known bug that Android might forget the status, and you have to pair again.
BLE and Classic Bluetooth
So you want to connect to both BLE devices and classic Bluetooth devices at the same time? Obviously, you’re asking too much. My experience is, the classic Bluetooth devices are more likely to be disconnected, and they won’t be able to connect until you ask the users to act as described above.
I haven’t figured any robust ways to get them always working. Please let me know if you’ve found solutions.
Too Many BLE Devices
If the user has scanned too many devices, he / she will be notified Unfortunately, Bluetooth share has stopped. And of course it’s a system issue, and it sucks especially when there’re BLE beacons changing address all the time.
OK, there’s a non-perfect solution. Also, for non-rooted devices, the user can turn off the Bluetooth to postpone the issue from happening. Alternatively, the user can do a factory reset when shit hits the fan, and wait for it to happen the next time. For rooted devices, you can manually edit the bt_config.xml file.
Best solution? Get a device with Android 4.4.3 or later.
There is some limitation on the number of BLE devices Android can connect to at the same time, and it seems the number is different on different versions. So better connect one by one.
This is not an Android issue, but more like a Samsung issue (works fine on Nexus, but haven’t checked other vendors):
Disconnect from GATT Server
So, hopefully, you have successfully connected to your GATT server, and after all the reading and writing, you need to disconnect:
Well, it may or may not work, and might throw exceptions, so better catch all Throwables.
Well, it’s a big mess in 4.3 and 4.4 (thanks to the new Bluedroid Bluetooth stack they introduced in 4.2), but luckily things seem to be more stable in 5.0. And hopefully, most devices could enjoy Lollipop somewhere in the future.
Among others, you will need to add the wearable module to your build.gradle file for both wearable app project and phone app project:
Then you should include your wearable app project into your phone app project:
Basic wearable app
Basically, you can run any Android application on your watch, though it has a small display and limited hardware support (e.g. some watch has no GPS support). You can also use the support library provided by Google for some common UI widgets:
This library also provides the handy way to load a different layout for square or round watches. In your main activity’s layout file:
The WatchViewStub view will load the corresponding layout based on the shape of the watch. Then in your activity:
Send and sync data
There are two ways to share data between your phone and watch:
Send a specific message to a certain node using the MessageApi.
Share data among all nodes using the DataApi. With this API, the data sent will be synchronized across all connected devices, which means the data will be pushed to a disconnected device if it gets connected later.
With both methods, the data are private to the application, so the develop doesn’t need to worry about the privacy nor security.
Send data with MessageApi
Once you have a connected GoogleApiClient, you can use the following code to send a message to a connected node:
Sync data with DataApi
Once you have a connected GoogleApiClient, you can use the following code to sync data across all connected nodes:
Receive data with WearableListenerService
To receive messages or data updates from other nodes, you must extend the WearableListenerService, whose life cycle is managed by the phone or the watch:
And register it in your AndroidManifest.xml:
Alternatively, for an activity or a fragment, you can use DataApi or MessageApi to register a temporary listener.
Yep, that’s it. Quite straight forward to get your app running on the watch, happy hacking!
If you haven’t set up Google Play services yet, please follow this tutorial.
First, update your AndroidManifest.xml file to request the permissions for accessing locations:
The two permissions allow you to control the accuracy of the requested locations, and you don’t have to request both for your app. If you only request the coarse location permission, the fetched location will be obfuscated. However, if you want to use the geofencing feature, you must request the ACCESS_FINE_LOCATION permission.
Connect Location Client
With the new GoogleApiClient class, you can connect all needed services at once, and Google Play services will handle all the permission requests, etc.:
Access Current Location
Once connected, you can easily fetch the current location:
Note that this getLastLocation() method might return null in case location is not available, though this happens very rarely. Also, it might return a location that is a bit old, so the client should check it manually.
Listen to Location Updates
Let’s extend the above code snippet:
The difference between setFastestInterval() and setInterval() is:
If the location updates is retrieved by other apps (or other location request in your app), your onLocationChanged() callback here won’t be called more frequently than the time set by setFastestInterval().
On the other hand, the location client will actively try to get location updates at the interval set by setInterval(), which has a direct impact on the power consumption of your app.
Then how about background location tracking? Do I need to implement a long-running Service myself? The answer is simply, no.
With this, your listener (it can be an IntentService, or a BroadcastReceiver) as defined in the PendingIntent will be triggered even if your app is killed by the system. The location updated will be sent with key FusedLocationProviderApi.KEY_LOCATION_CHANGED and a Location object as the value in the Intent:
With geofencing, your app can be notified when the device enters, stays in, or exits a defined area. Please note that geofencing requires ACCESS_FINE_LOCATION. Again, let’s keep extending the above sample:
Here, the loitering delay means the GEOFENCE_TRANSITION_DWELL will be notified 30 seconds after the device enters the area. There’re also limitations on the number of geofences (100 per app) and pending intents (5 per app) enforced.
When a geofence transition is triggered, you can find more details easily e.g. in an IntentService:
Finally, to remove geofences, you can simply use one of the overloaded LocationServices.GeofencingApi.removeGeofences() methods.
To enable mock locations, you must first request the corresponding permission (usually for your debug build only):
Then you can enable and set mock location:
To distinguish if the location is a mock one, a key of FusedLocationProviderApi.KEY_MOCK_LOCATION in the location object’s bundle extra will be set to true.
Once done, please remember to set the mock mode to false. If you forget that, the system will set it to false when your location client is disconnected.
That’s it for today. Happy coding and keep reading.
If you haven’t set up Google Play services SDK yet, please follow this tutorial. Today, we demonstrate how to use the cloud messaging / push notification.
Enable Cloud Messaging API
First, create a project in Google Developers Console for your app, and enable the Google Cloud Messaging for Android API. The project number will be used as the GCM sender ID.
Then, create a server key in Public API access with your server’s IP address (for testing purposes, you can use e.g. 0.0.0.0/0 to allow anybody to send messages with the generated API key). The generated API key will be used to authenticate your server.
Note: For devices running Android older than 4.0.4, it requires users to set up a Google account before using GCM.
Now, update your AndroidManifest.xml file, specifying the required permissions:
Register The Client
The following snippet shows how to register a client for cloud messaging:
Note that Google’s GCM server doesn’t handle localization nor message scheduling, so the client might also need to upload e.g. the preferred language and timezone to the server to improve user experiences.
In your AndroidManifest.xml, specify a listener:
Then you can handle the message yourself:
Send a Message
To send a push message, you can simply send a POST request to Google’s GCM server. The URL of the server is: https://gcm-http.googleapis.com/gcm/send.
The HTTP header must contain the following two:
The HTTP body is a JSON object, something like this:
The fields of the data object represent the key-value pairs of the message’s payload data. With the above example, the client will receive an intent with an extra of key key_1 and value value_1, and another extra of key key_2 and value value_2.
The priority can be either normal (default) or high. The messages marked as high priority will be sent immediately, even when the device is in Doze mode. For normal priority messages, they will be batched for devices in Doze mode, and will be discarded if the message expires while the device is in Doze mode.
For a complete list of allowed fields in the posted JSON, check here.
The response can contain the following status code:
200: The message is successfully processed.
400: The request JSON object is malformed.
401: The sender fails to authenticate itself.
5xx: Server error, the sender should respect the Retry-After and retry later.
More details of how to parse the response messages can be found here.
That’s it for today. Happy hacking and keep reading!