ZionSoft     About     Archive     Feed     Privacy

Wrapping Existing Code With RxJava

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.

Synchronous APIs

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:

Observable<String> observable = Observable.defer(
  new Func0<Observable<String>>() {
    @Override
    public Observable<String> call() {
      try {
        return Observable.just(getStringBlocking());
      } catch (Exception e) {
        return Observable.error(e);
      }
    }
  });

And if you want RxJava to do try...catch for you, you can use Observable.fromCallable():

Observable<String> observable = Observable.fromCallable(
  new Callable<String>() {
    @Override
    public String call() throws Exception {
      return getStringBlocking();
    }
  });

Asynchronous APIs

Usually, existing code uses callbacks to support asynchronous operations, e.g. in Android we can request location updates like this:

locationManager.requestLocationUpdates(
  LocationManager.GPS_PROVIDER, 1000L, 10.0F,
  new LocationListener() {
    @Override
    public void onLocationChanged(Location location) {
    }

    ...
  });

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:

Observable<Location> observable = Observable.fromEmitter(
  new Action1<AsyncEmitter<Location>>() {
    @Override
    public void call(final AsyncEmitter<Location> emitter) {
      final LocationListener locationListener = new LocationListener() {
        @Override
        public void onLocationChanged(Location location) {
          // emits location
          emitter.onNext(location);
        }

        ...
      };

      emitter.setCancellation(new AsyncEmitter.Cancellable() { 
        @Override 
        public void cancel() throws Exception {
          // stops location updates when unsubscribed
          locationManager.removeUpdates(locationListener);
        });

      locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
        1000L, 10.0F, locationListener);

      // if you also emit onError() or onComplete(),
      // the framework will make sure the Observable
      // contract is fullfilled
    }
     // let the framework to worry about backpressure
  }, AsyncEmitter.BackpressureMode.BUFFER);

Enjoy.

Create Chooser Intent with Packages Excluded

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:

@Nullable
private static Intent createChooserExcludingPackage(
        Context context, String packageToExclude, String text) {
    // 1) gets all activities that can handle the sharing intent
    final Intent sendIntent = new Intent(Intent.ACTION_SEND)
            .setType("text/plain");
    final PackageManager pm = context.getPackageManager();
    final List<ResolveInfo> resolveInfoList = pm.queryIntentActivities(sendIntent, 0);
    final int size = resolveInfoList.size();
    if (size == 0) {
        return null;
    }

    // 2) now let's filter by package name
    final ArrayList<Intent> filteredIntents = new ArrayList<>(size);
    for (int i = 0; i < size; ++i) {
        final ResolveInfo resolveInfo = resolveInfoList.get(i);
        final String packageName = resolveInfo.activityInfo.packageName;
        if (!packageToExclude.equals(packageName)) {
            // creates a LabeledIntent with custom icon and text
            final LabeledIntent labeledIntent = new LabeledIntent(
                    packageName, resolveInfo.loadLabel(pm), resolveInfo.getIconResource());
            labeledIntent.setAction(Intent.ACTION_SEND).setPackage(packageName)
                    .setComponent(new ComponentName(packageName, resolveInfo.activityInfo.name))
                    .setType("text/plain").putExtra(Intent.EXTRA_TEXT, text);
            filteredIntents.add(labeledIntent);
        }
    }

    // 3) creates new chooser intent
    final Intent chooserIntent = Intent.createChooser(filteredIntents.remove(0),
            context.getText(R.string.text_share_with));
    final int extraIntents = filteredIntents.size();
    if (extraIntents > 0) {
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
                filteredIntents.toArray(new Parcelable[extraIntents]));
    }
    return chooserIntent;
}

The above code is taken from here. Enjoy and happy coding!

Write Own Osmosis Plugin

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:

<?xml version="1.0" ?><!DOCTYPE plugin PUBLIC "-//JPF//Java Plug-in Manifest 1.0"
    "http://jpf.sourceforge.net/plugin_1_0.dtd">
<!-- Here, provides a unique ID and version for your plugin. -->
<plugin id="MyPlugin" version="1.0">
    <requires>
        <import plugin-id="org.openstreetmap.osmosis.core.plugin.Core" reverse-lookup="false" />
    </requires>

    <runtime>
        <library id="mycode" path="/" type="code" />
    </runtime>

    <!-- Describes where the plugin plugs into. -->
    <extension id="MyPlugin"
        plugin-id="org.openstreetmap.osmosis.core.plugin.Core" point-id="Task">
        <parameter id="name" value="MyPlugin" />
        <!-- Here, give the fully qualified name for your class loader. -->
        <parameter id="class" value="net.zionsoft.sample.osmosisplugin.MyPluginLoader" />
    </extension>
</plugin>

As we can see, the plugin is loaded by MyPluginLoader:

public class MyPluginLoader implements PluginLoader {
    @Override
    public Map<String, TaskManagerFactory> loadTaskFactories() {
        // the map describes how to load the plugin
        // with the following statement, you can load the task
        // created by MyFactory using:
        // osmosis --read-pbf latest.osm.pbf --my-plugin
        // you can also create multiple factories that create
        // different tasks if needed
        HashMap<String, TaskManagerFactory> map = new HashMap<>();
        map.put("my-plugin", new MyFactory());
        return map;
    }
}

The following shows MyFactory that creates the real tasks:

public class MyFactory extends TaskManagerFactory {
    @Override
    protected TaskManager createTaskManagerImpl(TaskConfiguration taskConfiguration) {
        // the provided configuration includes the argument you pass to osmosis,
        // which can be used to config the task to be created
        // e.g. if you started osmosis like this:
        // osmosis --read-pbf latest.osm.pbf --my-plugin key=value
        // you can get fetch the passed argument like this:
        // String value = getStringArgument(taskConfiguration, "key", null);

        MyTask myTask = new MyTask();
        return new SinkManager(taskConfiguration.getId(), myTask, taskConfiguration.getPipeArgs());
    }
}

Now, a simple task that does nothing but print some messages:

public class MyTask implements Sink {
    @Override
    public void initialize(Map<String, Object> map) {
        // initializes resources you need
        System.out.println("initialize()");
    }

    @Override
    public void process(EntityContainer entityContainer) {
        // processes each entity
        Entity entity = entityContainer.getEntity();
        EntityType entityType = entity.getType();
        System.out.println("process(): " + entityType);

        switch (entityType) {
            case Bound:
                Bound bound = (Bound) entity;
                System.out.println(bound.toString());
                break;
            case Node:
                Node node = (Node) entity;
                System.out.println(node.toString());
                break;
            case Way:
                Way way = (Way) entity;
                System.out.println(way.toString());
                break;
            case Relation:
                Relation relation = (Relation) entity;
                System.out.println(relation.toString());
                break;
        }
    }

    @Override
    public void complete() {
        // makes sure all info is fully persisted
        System.out.println("complete()");
    }

    @Override
    public void release() {
        // releases resources
        System.out.println("release()");
    }
}

Installation

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:

Oct 13, 2015 9:05:39 PM org.openstreetmap.osmosis.core.Osmosis run
INFO: Osmosis Version 0.42-6-gf39a160-dirty
Oct 13, 2015 9:05:40 PM org.openstreetmap.osmosis.core.Osmosis run
INFO: Preparing pipeline.
Oct 13, 2015 9:05:40 PM org.openstreetmap.osmosis.core.Osmosis run
INFO: Launching pipeline execution.
initialize()
Oct 13, 2015 9:05:40 PM org.openstreetmap.osmosis.core.Osmosis run
INFO: Pipeline executing, waiting for completion.
process(): Bound
Bound(top=60.2359955, bottom=60.2358955, left=24.7136472, right=24.7186472)
process(): Node
Node(id=506188159, #tags=0)
process(): Node
Node(id=518411565, #tags=0)
process(): Node
Node(id=2009423577, #tags=0)
process(): Way
Way(id=23123806, #tags=2)
complete()
release()
Oct 13, 2015 9:05:53 PM org.openstreetmap.osmosis.core.Osmosis run
INFO: Pipeline complete.
Oct 13, 2015 9:05:53 PM org.openstreetmap.osmosis.core.Osmosis run
INFO: Total execution time: 13088 milliseconds.

Yes, it’s that simple to create your own osmosis plugin, and time to hack now!

BLE on Android

Android supported Bluetooth Low Energy / Bluetooth Smart since 4.3 / API level 18. However, it’s not nice, in many ways.

Device Scanning

To scan e.g. heart rate monitors, you’re supposed to use code like this:

// scan for devices providing a certain service
bluetoothAdapter.startLeScan(new UUID[]{serviceUuid},
  new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
      // wow, it should work
    }
  });
 
// or scan for all nearby BLE devices
bluetoothAdapter.startLeScan(new BluetoothAdapter.LeScanCallback() {
  @Override
  public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
    // yeah, it must work, right?
  }
});

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.

And of course, Android and iOS hate each other, so they refuse to talk to each other.

Paired BLE Device

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.

Concurrent Connections

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.

Threading Issue

This is not an Android issue, but more like a Samsung issue (works fine on Nexus, but haven’t checked other vendors):

private final BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        // on certain devices, this won't work
        // you must connect in another thread
        BluetoothGatt gatt = device.connectGatt(context, false, gattCallback);
    }
}

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:

gatt.disconnect();
gatt.close();

Well, it may or may not work, and might throw exceptions, so better catch all Throwables.

Conclusion

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.

Have fun with Android Wear

To use Android Wear, you’ll need a hosting device (e.g. a phone or a tablet) with Android 4.3 or above, with BLE support. To set up the Google Play services that is used for communication between phone and watch, please follow this tutorial.

Among others, you will need to add the wearable module to your build.gradle file for both wearable app project and phone app project:

compile 'com.google.android.gms:play-services-wearable:7.0.0'

Then you should include your wearable app project into your phone app project:

debugWearApp project(path:':MyAndroidWear', configuration: 'debug')
releaseWearApp project(path:':MyAndroidWear', configuration: 'release')

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:

dependencies {
  compile 'com.google.android.support:wearable:1.1.0'
}

This library also provides the handy way to load a different layout for square or round watches. In your main activity’s layout file:

<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.WatchViewStub
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:id="@+id/watch_view_stub"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  app:rectLayout="@layout/rect_activity_main"
  app:roundLayout="@layout/round_activity_main"/>

The WatchViewStub view will load the corresponding layout based on the shape of the watch. Then in your activity:

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
 
  setContentView(R.layout.activity_main);
  WatchViewStub watchViewStub = (WatchViewStub) findViewById(R.id. watch_view_stub);
  watchViewStub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
    @Override
    public void onLayoutInflated(WatchViewStub stub) {
      // the layout is fully inflated
    }
  });
}

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:

// first finds all the connected nodes
Wearable.NodeApi.getConnectedNodes(googleApiClient).setResultCallback(
  new ResultCallback<NodeApi.GetConnectedNodesResult>() {
    @Override
    public void onResult(NodeApi.GetConnectedNodesResult getConnectedNodesResult) {
      List<Node> nodes = getConnectedNodesResult.getNodes();
      if (nodes.size() == 0) {
        // no connected nodes
        return;
      }
      // sends a message to the specified path for the first connected node
      String nodeId = nodes.get(0).getId();
      byte[] data = ...;
      Wearable.MessageApi.sendMessage(googleApiClient, nodeId, "/some/random/path", data);
    }
  });

Sync data with DataApi

Once you have a connected GoogleApiClient, you can use the following code to sync data across all connected nodes:

PutDataMapRequest putDataMapRequest = PutDataMapRequest.create("/some/random/path");
DataMap dataMap = putDataMapRequest.getDataMap();
// you can put different data here
dataMap.putLong("key 1", 12345L);
Wearable.DataApi.putDataItem(googleApiClient, putDataMapRequest.asPutDataRequest());

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:

public class WearableListener extends WearableListenerService {
  @Override
  public void onMessageReceived(MessageEvent messageEvent) {
    // this is called when it receives one single message from
    // a connected node
  }
 
  @Override
  public void onDataChanged(DataEventBuffer dataEvents) {
    // this is called when one or more data is created, updated
    // or deleted using the DataApi
    // note:
    // 1) if the same data is updated several times, you might
    // only be notified once, with the final state of the data
    // 2) it might contain more than one data events
    // 3) the provided buffer is only valid till the end of this
    // method
    for (DataEvent dataEvent : dataEvents) {
      DataItem dataItem = dataEvent.getDataItem();
      Uri dataItemUri = dataItem.getUri();
      // the path is the one provided when sync the data with DataApi
      String path = dataItemUri.getPath();
      if (!"/some/random/path".equals(path)) {
        continue;
      }
      switch (dataEvent.getType()) {
        case DataEvent.TYPE_CHANGED:
          // the data is created or updated
          // note that if it is updated several times with the
          // same data, this method won't be called
          break;
        case DataEvent.TYPE_DELETED:
          // the data is deleted
          // note that if the data is not deleted, it will be
          // "persisted" until the application is removed
          break;
      }
    }
  }
}

And register it in your AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="net.zionsoft.sample">
  <application>
    <service android:name=".MyWearableListenerService">
      <intent-filter>
        <action android:name="com.google.android.gms.wearable.BIND_LISTENER"/>
      </intent-filter>
    </service>
  </application>
</manifest>

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!

Play with Google Play Services 2 - Locations

If you haven’t set up Google Play services yet, please follow this tutorial.

Permissions

First, update your AndroidManifest.xml file to request the permissions for accessing locations:

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

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.:

public class MainActivity extends Activity 
        implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
    private GoogleApiClient mGoogleClient;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        // you can also add more APIs and scopes here
        mGoogleClient = new GoogleApiClient.Builder(this, this, this)
                .addApi(LocationServices.API)
                .build();
    }
 
    @Override
    protected void onStart() {
        super.onStart();
 
        mGoogleClient.connect();
    }
 
    @Override
    protected void onStop() {
        mGoogleClient.disconnect();
 
        super.onStop();
    }
 
    @Override
    public void onConnected(Bundle connectionHint) {
        // this callback will be invoked when all specified services are connected
    }
 
    @Override
    public void onConnectionSuspended(int cause) {
        // this callback will be invoked when the client is disconnected
        // it might happen e.g. when Google Play service crashes
        // when this happens, all requests are canceled,
        // and you must wait for it to be connected again
    }
 
    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        // this callback will be invoked when the connection attempt fails
 
        if (connectionResult.hasResolution()) {
            // Google Play services can fix the issue
            // e.g. the user needs to enable it, updates to latest version
            // or the user needs to grant permissions to it
            try {
                connectionResult.startResolutionForResult(this, 0);
            } catch (IntentSender.SendIntentException e) {
                // it happens if the resolution intent has been canceled,
                // or is no longer able to execute the request
            }
        } else {
            // Google Play services has no idea how to fix the issue
        }
    }
}

Access Current Location

Once connected, you can easily fetch the current location:

// fetch the current location
Location location = LocationServices.FusedLocationApi.getLastLocation(mGoogleClient);

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:

public class MainActivity extends Activity
        implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
        LocationListener {
    ...
 
    @Override
    public void onConnected(Bundle dataBundle) {
        ...
 
        // start listening to location updates
        // this is suitable for foreground listening,
        // with the onLocationChanged() invoked for location updates
        LocationRequest locationRequest = LocationRequest.create()
                .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY)
                .setFastestInterval(5000L)
                .setInterval(10000L)
                .setSmallestDisplacement(75.0F);
        LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleClient, locationRequest, this);
    }
 
    @Override
    public void onLocationChanged(Location location) {
        // this callback is invoked when location updates
    }
 
    ...
}

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.

@Override
public void onConnected(Bundle dataBundle) {
    ...
    LocationRequest locationRequest = LocationRequest.create()
            .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY)
            .setFastestInterval(5000L)
            .setInterval(10000L)
            .setSmallestDisplacement(75.0F);
    PendingIntent pendingIntent = PendingIntent.getService(this, 0,
            new Intent(this, MyLocationHandler.class),
            PendingIntent.FLAG_UPDATE_CURRENT);
    LocationServices.FusedLocationApi.requestLocationUpdates(
            mGoogleClient, locationRequest, pendingIntent);
}

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:

public class MyLocationHandler extends IntentService {
    public MyLocationHandler() {
        super("net.zionsoft.example.MyLocationHandler");
    }
 
    @Override
    protected void onHandleIntent(Intent intent) {
        final Location location = intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
        // happy playing with your location
    }
}

Geofencing

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:

public class MainActivity extends Activity
        implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
    ...
 
    @Override
    public void onConnected(Bundle dataBundle) {
        ...
 
        // adds geofencing
        ArrayList<Geofence> geofences = new ArrayList<Geofence>();
        geofences.add(new Geofence.Builder()
                .setRequestId("unique-geofence-id")
                .setCircularRegion(60.1708, 24.9375, 1000)
                .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER
                        | Geofence.GEOFENCE_TRANSITION_DWELL
                        | Geofence.GEOFENCE_TRANSITION_EXIT)
                .setLoiteringDelay(30000)
                .build());
        PendingIntent pendingIntent = PendingIntent.getService(this, 0,
                new Intent(this, MyGeofenceHandler.class),
                PendingIntent.FLAG_UPDATE_CURRENT);
        LocationServices.GeofencingApi.addGeofences(
                mGoogleClient, geofences, pendingIntent);
    }
 
    ...
}

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:

@Override
protected void onHandleIntent(Intent intent) {
    GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
    ...
}

Finally, to remove geofences, you can simply use one of the overloaded LocationServices.GeofencingApi.removeGeofences() methods.

Mock Locations

To enable mock locations, you must first request the corresponding permission (usually for your debug build only):

<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>

Then you can enable and set mock location:

@Override
public void onConnected(Bundle dataBundle) {
    ...
    LocationServices.FusedLocationApi.setMockMode(mGoogleClient, true);
    LocationServices.FusedLocationApi.setMockLocation(mGoogleClient, mockLocation);
}

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.

Play with Google Play Services 6 - Push Notification

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.

Update AndroidManifest.xml

Now, update your AndroidManifest.xml file, specifying the required permissions:

<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
 
<permission
    android:name="com.example.app.permission.C2D_MESSAGE"
    android:protectionLevel="signature" />
<uses-permission android:name="com.example.app.permission.C2D_MESSAGE" />

Register The Client

The following snippet shows how to register a client for cloud messaging:

// this might invoke network calls, so call me in a worker thread
private void registerCloudMessaging() {
    // repeated calls to this method will return the same token
    // a new registration is needed if the app is updated or backup & restore happens
    final String token = InstanceID.getInstance(this).getToken("my-GCM-sender-ID",
        GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
 
    // then uploads the token to your server
 
    // or, if the client wants to subscribe to certain topics, use this method
    GcmPubSub.getInstance(this).subscribe(token, "/topics/your_topic_name", null);
}

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.

Receive Messages

In your AndroidManifest.xml, specify a listener:

<application ...>
    <receiver
        android:name=".MyPushNotificationReceiver"
        android:permission="com.google.android.c2dm.permission.SEND" >
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <category android:name="com.example.app" />
        </intent-filter>
    </receiver>
</application>

Then you can handle the message yourself:

public class MyPushNotificationReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // a null or "gcm" message type indicates a regular message,
        // and you can get the message details as described in section 5
        // "deleted_messages" indicates some pending messages are deleted by server
        String messageType = intent.getStringExtra("message_type");
 
        // indicates the sender of the message, or the topic name
        String from = intent.getStringExtra("from");
    }
}

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:

  • Authorization:key=
  • Content-Type: application/json

The HTTP body is a JSON object, something like this:

{
  "to": "client_registration_id_or_topic_name",
  "data": {
    "key_1": "value_1",
    "key_2": "value_2"
  },
  "priority": "normal"
}

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!