ZionSoft     About     Archive     Feed     Privacy

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:

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)
    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());
                    .setComponent(new ComponentName(packageName, resolveInfo.activityInfo.name))
                    .setType("text/plain").putExtra(Intent.EXTRA_TEXT, text);

    // 3) creates new chooser intent
    final Intent chooserIntent = Intent.createChooser(filteredIntents.remove(0),
    final int extraIntents = filteredIntents.size();
    if (extraIntents > 0) {
                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"
<!-- Here, provides a unique ID and version for your plugin. -->
<plugin id="MyPlugin" version="1.0">
        <import plugin-id="org.openstreetmap.osmosis.core.plugin.Core" reverse-lookup="false" />

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

    <!-- 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" />

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

public class MyPluginLoader implements PluginLoader {
    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 {
    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 {
    public void initialize(Map<String, Object> map) {
        // initializes resources you need

    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;
            case Node:
                Node node = (Node) entity;
            case Way:
                Way way = (Way) entity;
            case Relation:
                Relation relation = (Relation) entity;

    public void complete() {
        // makes sure all info is fully persisted

    public void release() {
        // releases resources


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.
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)
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() {
    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() {
  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() {
    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:


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.

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"?>

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

protected void onCreate(Bundle savedInstanceState) {
  WatchViewStub watchViewStub = (WatchViewStub) findViewById(R.id. watch_view_stub);
  watchViewStub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
    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
  new ResultCallback<NodeApi.GetConnectedNodesResult>() {
    public void onResult(NodeApi.GetConnectedNodesResult getConnectedNodesResult) {
      List<Node> nodes = getConnectedNodesResult.getNodes();
      if (nodes.size() == 0) {
        // no connected nodes
      // 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 {
  public void onMessageReceived(MessageEvent messageEvent) {
    // this is called when it receives one single message from
    // a connected node
  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)) {
      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
        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

And register it in your AndroidManifest.xml:

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

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.


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;
    protected void onCreate(Bundle savedInstanceState) {
        // you can also add more APIs and scopes here
        mGoogleClient = new GoogleApiClient.Builder(this, this, this)
    protected void onStart() {
    protected void onStop() {
    public void onConnected(Bundle connectionHint) {
        // this callback will be invoked when all specified services are connected
    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
    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 {
    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()
        LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleClient, locationRequest, this);
    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.

public void onConnected(Bundle dataBundle) {
    LocationRequest locationRequest = LocationRequest.create()
    PendingIntent pendingIntent = PendingIntent.getService(this, 0,
            new Intent(this, MyLocationHandler.class),
            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() {
    protected void onHandleIntent(Intent intent) {
        final Location location = intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
        // happy playing with your location


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 {
    public void onConnected(Bundle dataBundle) {
        // adds geofencing
        ArrayList<Geofence> geofences = new ArrayList<Geofence>();
        geofences.add(new Geofence.Builder()
                .setCircularRegion(60.1708, 24.9375, 1000)
                        | Geofence.GEOFENCE_TRANSITION_DWELL
                        | Geofence.GEOFENCE_TRANSITION_EXIT)
        PendingIntent pendingIntent = PendingIntent.getService(this, 0,
                new Intent(this, MyGeofenceHandler.class),
                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:

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:

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. 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"/>
    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 ...>
        android:permission="com.google.android.c2dm.permission.SEND" >
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <category android:name="com.example.app" />

Then you can handle the message yourself:

public class MyPushNotificationReceiver extends BroadcastReceiver {
    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!

Play with Google Play Services 3 - Map

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

Get Maps API Key

First, create a project for your app in Google Developers Console, and enable Google Maps Android API v2.

Then get you the signing certificate’s fingerprint:

# displays fingerprint of your debug certificate
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
# displays fingerprint of your release certificate
keytool -list -keystore <your keystore>

Then use the SHA-1 fingerprint and your app’s package name to generate a new key for API access from Google Developers Console.

Update AndroidManifest.xml

Now update your app’s AndroidManifest.xml file, specifying the permissions, feature required, and the map key:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>

Add a Map

The easiest way to show a map is to add a MapFragment (or a SupportMapFragment for older devices) to your activity. Another approach is to add a MapView to your activity or fragment.

To add a map view showing satellite maps with an XML file:

<?xml version="1.0" encoding="utf-8"?>
        map:mapType="satellite" />

Then in your activity:

public class MainActivity extends Activity {
    private MapView mMapView;
    protected void onCreate(Bundle savedInstanceState) {
        // if you use a map fragment, it will take care of its life-cycle management
        // if you use a map view, you need to forward all the following events to it
        mMapView = (MapView) findViewById(R.id.map_view);
    protected void onResume() {
    protected void onPause() {
    protected void onDestroy() {
    protected void onSaveInstanceState(Bundle outState) {
    public void onLowMemory() {

GoogleMap Object

Once you have the map ready, you can use the getMap() method (or the getMapAsync() to get reference to the map asynchronously) of the map view or fragment to get a GoogleMap object. If this method returns null, it means the map is not ready yet (e.g. because the Google Play services is not available). It serves as the entry point for interactions with the map:

  • To configure the UI, e.g. show / hide my location button, enable / disable gestures, use the UiSettings object fetched using GoogleMap.getUiSettings() method.
  • To convert between coordinates on the map and the location on the screen, use the Projection object fetched using GoogleMap.getProjection() method.
  • You can uses GoogleMap.setMyLocationEnabled() method to show or hide the blue dot representing device’s current location, but to get the current location you should follow my previous tutorial on locations.
  • To listen to map click events, you can simply set the corresponding listener using GoogleMap.setOnMapClickListener() or GoogleMap.setOnMapLongClickListener() method.
  • You can set the camera (i.e. which part of the map should be shown) using the GoogleMap.animateCamera() (with animation) or GoogleMap.moveCamera() (no animation) method. To listener to camera changes (e.g. triggered by user), you can specify the corresponding listener using GoogleMap.setOnCameraChangeListener().

Also, you can set map types (normal, satellite, etc.), enable / disable indoor maps, 3D buildings, etc. using the GoogleMap object.

Markers and Info Windows

Markers can be used to indicate locations or other important information on the map, with more detailed information shown in the info window.

The following snippet adds a marker with a custom icon to the map, and displays a default info window showing “Hello, Helsinki!”:

protected void onResume() {
    mGoogleMap = mMapView.getMap();
    BitmapDescriptor bitmap = ...
    Marker addedMarker = mGoogleMap.addMarker(new MarkerOptions()
        .position(new LatLng(60.1708, 24.9375))
        .title("Hello, Helsinki")

One thing to note when using e.g. BitmapDescriptorFactory is, you might need to use MapsInitializer to explicitly initialize the map.

To remove a marker, you can use the Marker.remove() method, or the GoogleMap.clear() method to remove all added markers, shapes, etc.

If you want to animate a marker on the map (e.g. use a custom marker to indicate current location instead of the boring blue dot), all you need is simply update its position periodically using the Marker.setPosition() method.

When the user clicks on a marker, by default it shows an info window with some title and snippet defined when the marker was added. But you can also listen to the click event:

mGoogleMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() {
    public boolean onMarkerClick(Marker marker) {

If the onMarkerClick() method returns true, it means the click event has been consumed. Otherwise, if false is returned, it will execute the default behavior, i.e. moving the camera to the marker, and showing the info window.

To provide a customized info window:

mGoogleMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
    public View getInfoWindow(Marker marker) {
        // this will be first called
        // the returned view will be used as the entire info window
    public View getInfoContents(Marker marker) {
        // this will only be called if getInfoWindow() returns null
        // the returned view will be used inside the default info window frame

The info window is rendered as an image. It means any subsequent changes won’t be visible unless you re-open the info window, and the info window can only listen to one click event for the whole info window.

Circles, Polygons, and Polylines

To add those shapes to the map, you basically follow the same pattern:

Circle addedCircle = mGoogleMap.addCircle(new CircleOptions()
    .center(new LatLng(60.1708, 24.9375))
// when adding a polygon, the system will automatically connect the
// last coordinate to the first one
Polygon addedPolygon = mGoogleMap.addPolygon(new PolygonOptions()
    .add(new LatLng(60, 24), new LatLng(61, 24), new LatLng(61, 25), new LatLng(60, 25));
// the system won't automatically connect the coordinates
// thus you can see the difference compared to the above polygon
Polyline addedPolyline = mGoogleMap.addPolyline(new PolylineOptions()
    .add(new LatLng(60, 24), new LatLng(61, 24), new LatLng(61, 25), new LatLng(60, 25));

Ground and Tile Overlays

The main difference between these two overlays is: the ground overlay provides a single image for a fixed location on the map, while the tile overlay provides a set of images for different locations at different zoom level on the map.

To add a ground overlay:

GroundOverlay addedGroundOverlay = mGoogleMap.addGroundOverlay(new GroundOverlayOptions()
    .anchor(0.5, 0.5)
    .position(new LatLng(60.1708, 24.9375), 100, 100));

To add a tile overlay:

TileProvider tileProvider = new TileProvider() {
    public Tile getTile (int x, int y, int zoom) {
        // a Tile object consists of raw image data, as well its width and height
        // the map is divided into a 2^zoom x 2^zoom grid
        // for each grid, the system targets 256 x 256 dp
        // if TileProvider.NO_TILE is returned,
        // it means you don't want to provide tiles yourself
        // if null is returned, it means the tile could not be found now,
        // and will be tried again later
TileOverlay addedTileOverlay = mGoogleMap.addTileOverlay(new TileOverlayOptions()

That’s it for today. Happy hacking and keep reading :)