Google Play Services

A term that you will encounter a fair bit as an Android developer is “Google Play Services”, or “Play Services” for short. This is your gateway into a series of proprietary capabilities that Google has layered on top of Android. Many of these capabilities are tied to Google’s servers and services, such as ads and Google Drive.

However, these capabilities, while usually free from monetary cost to the developer, are not free from problems or controversy.

What Is Google Play Services?

Google Play Services is a “kitchen sink” term, encompassing a wide range of things from the standpoint of developers and users alike.

…From the Standpoint of Developers?

The Play Services SDK allows you to integrate your Android app with a number of Google proprietary services, from leaderboard management for games to interacting with Chromecast devices. Many, but not all, of these services are tied to Google servers. Many, but not all, of these services will require some sort of API key as a result.

The SDK comes in the form of an Android library project that you link into your app, giving you access to classes and methods that let you add maps, or payment options, or push message receipt into your Android apps.

Note that while the name “Play Services” contains the word “services”, Play Services is merely an API, one that does not directly have anything to do with services or system services.

…From the Standpoint of Users of Google Play Devices?

In Western countries, the common perception is that all Android devices are part of the Google Play world. These devices will have the Play Services Framework pre-installed from the device manufacturer and silently updated over the air by Google. Apps that use the Play Services SDK in theory can use all of the SDK’s available APIs on all devices equipped with the Play Services Framework.

In practice, older devices (particularly Android 2.x) will have some number of limitations related to Play Services, not the least of which being the lack of automatic over-the-air updates. As many developers are now setting their minSdkVersion to be something newer (e.g., 15), this particular class of problems will tend to fall by the wayside.

…From the Standpoint of the Android Ecosystem?

Google’s continued expansion of the Play Services SDK, sometimes at the expense of Android itself, has not proven to be universally popular:

What Is In the Play Services SDK?

As mentioned earlier, the Play Services SDK is vast. The following sub-sections outline some of the major pieces of the Play Services SDK, what Gradle dependency pulls them in, and what independent alternatives exist (if any).

Android Pay / Google Wallet

Google has tried a couple of times to get into the mobile payments market, starting with Google Wallet, which has now morphed into Android Pay. If you want to allow users to purchase goods and services through your app, and you want to allow those users to pay via Android Pay, you can use this portion of the Play Services SDK.

Wear OS

To communicate from a device running an open source operating system (Android, on a phone or tablet) to a device running an open source operating system (Android, on an Wear OS device), you have to use a proprietary, closed-source library.

It is possible to show a Notification on a Wear device straight from the Android SDK. It is also possible to create a Wear app that exists standalone straight from the Android SDK. But if you want to send data to the Wear device from the phone or tablet, or vice versa, that requires the Wear portion of the Play Services SDK.

This library provides a few discrete APIs for communication:

Google+

The documentation and business proposition for the Google+ API is a bit limited at this time. However, it appears that you can:

Google Account Login / Sign In with Google

Rather than maintain your own account system, your app could ask the users to sign into their Google account as part of using your app.

Google Analytics

“Analytics” refers to tracking usage. Web analytics uses a mix of Web server logs, tracking cookies, and the like to determine popular Web pages, navigation flows, time spent in certain areas of a site, and so forth. Mobile analytics tracks usage within an app: certain activities, certain operations, etc.

Google Analytics is very popular for Web sites, and Google extended this to a mobile API designed for tracking app usage.

There are countless analytics services with Android APIs (e.g., Flurry) beyond Google’s. While there appear to be few self-hosted or open source solutions, analytics data collection is not especially difficult to implement on your own, if you would prefer to keep this information more private. Data analysis is where the challenges with home-grown solutions arise. Or, you could simply not collect this sort of information.

Google App Indexing

Google App Indexing, among other things, allows for “deep links” into an Android app, surfaced from Google search results. That, on its own, does not require any particular proprietary APIs. However, to allow Google to discover these “deep links”, it appears that you need to use a custom app-indexing API.

Google App Invites

Google’s App Invites service allows your users to annoy their contacts, bugging them to install your app.

A simpler, albeit less slick, solution is to allow the user to send messages from your app with a link back to your app from its distribution channel (e.g., Play Store), such as via an ACTION_SEND Intent.

Google Cast

Google Cast can be thought of as a control protocol for Google Cast-enabled receivers. Through a Google-supplied SDK (or other means), Google Cast client apps (“senders”) can direct a Google Cast-enabled receiver to play, pause, rewind, fast-forward, etc. a stream. Android TV devices and Chromecast devices are the primary Cast-enabled receivers.

Google Cast does assume that, in general, the media receiver runs its own OS and is capable of playing streaming media without ongoing assistance from the Google Cast client. Hence, the client is not “locked into” having to keep feeding content to the Google Cast client, allowing the user to go off and do other things with that client while playback is going on.

Chromecast offers up remote playback media routes and works with RemotePlaybackClient, as is discussed in the chapter on MediaRouter. The sample app for RemotePlaybackClient was tested on a Chromecast.

If you want greater control than is offered via RemotePlaybackClient, though, you can use the Cast SDK. However, using the Cast SDK will tie you to Google Cast — and some of its restrictions, both technical and legal — but will give you greater developer control over the behavior of both the Google Cast device and your app.

As noted above, RemotePlaybackClient, along with the Presentation API, offer a significant subset of what the Cast SDK offers.

Google Cloud Messaging

Google Cloud Messaging – GCM for short — asynchronously delivers notifications from the Internet (“cloud”) to Android devices. Rather than the device waking up and polling on a regular basis at the behest of your app, your app can register for notifications and then wait for them to arrive. GCM is engineered with efficiency in mind:

The proper use of GCM means better battery life for your users. It can also reduce the amount of time your code runs, which helps you stay out of sight of users looking to pounce on background tasks and eradicate them with task killers.

GCM has gone through four revisions of its API, including the 2016 rebranding of it as Firebase Cloud Messaging (FCM). Be sure to use up-to-date references and examples when adding GCM/FCM to your apps.

You may also encounter references to “C2DM”, GCM’s precursor. C2DM debuted in 2010 and quickly became popular, for everything from triggering near-real-time data synchronization (e.g., Remember the Milk to-do list updates) to lightweight coordination between multiple players in a game. However, C2DM was a Google Labs product and in perpetual beta form. When Google Labs was shut down, C2DM was in limbo: not canceled, but not converted into an actual product. In 2012, GCM formally replaced C2DM, and in 2015, C2DM was shut down entirely. Hence, while high-level concepts about push messaging from the C2DM era might still be relevant to you, any actual C2DM-related code will be useless.

Other devices from outside the Google Play ecosystem may offer their own counterparts to GCM. Independent push implementations can range from XMPP and MQTT to simple WebSockets, though these have limitations when compared to GCM.

Google Drive

Google Drive is Google’s hosted file-storage service. Via Drive APIs in the Play Services SDK, you can work indirectly with the user’s Google Drive-hosted content, including creating and deleting files, plus searching through files for ones that meet particular search criteria.

Note that some of this functionality is available via the Storage Access Framework in Android 4.4+, with the advantage that it works across multiple content sources, not just Google Drive.

Other services (e.g., Dropbox) have their own APIs as well.

Google Fit

Google Fit is Google’s wearable sensor initiative, for “smartbands” and related gadgets. Through the Fit APIs, you can detect Fit gadgets associated with a user’s device, read data from those gadgets’ sensors (e.g., heart rate), and so forth.

Other manufacturers in this space (e.g., Fitbit) have their own SDKs as well.

Google Location Services

This portion of the Play Services SDK offers the “fused location provider”. This combines GPS and network sources of location data, plus sensor information, to try to offer better location information with less power draw. For example, if the sensors suggest that the device is not moving, the fused location provider can scale back how aggressively it uses the location sources, since the location probably is not changing.

This library also offers a “geofencing” implementation, where you ask the Play Services SDK to keep track of certain locations and let you know if the device gets within a certain distance of those locations.

This book has a chapter on the fused location provider.

Google Maps

Android has offered integrated Google Maps to developers since the outset. With the introduction of Maps V2 in 2012, this capability was folded into the Play Services SDK. Through Maps V2, you can embed a map powered by Google Maps into your application, complete with markers and popups, lines and shaded areas, and so on.

This book has a chapter on Maps V2.

Due to the popularity of embedded maps, other manufacturers (e.g., Amazon, Blackberry) have offered their own map engines, often with APIs that attempt to mimic that of Maps V2 (or perhaps its predecessor, now known as Maps V1). Beyond that, there is the OpenStreetMap project, for which Android libraries are available.

Google Mobile Ads / AdMob

Google is an advertising company. They offer the Google Mobile Ads SDK (a.k.a., AdMob for Android) as part of the Play Services SDK, for you to be able to add banners, interstitials, and other forms of advertising to your app.

There are other competing mobile ad networks that you could consider, though you may be better served focusing on coming up with a better business model.

Google Mobile Vision

Google has a variety of APIs, grouped under the “Mobile Vision” banner, designed for detecting specific sorts of objects or other information in still photos and videos. These include:

Android’s native camera API has some amount of face recognition, though not to the level of the Face API in the Mobile Vision SDK.

There are a variety of barcode scanning apps (e.g., the legendary ZXing Barcode Scanner) and libraries (e.g., ZBar) that one can use independently of the Play Services SDK.

Google Nearby

Google Nearby offers a pair of APIs for communication between nearby devices.

The Nearby Messages API offers a publish-and-subscribe messaging framework, designed for sending small blocks of data between Internet-connected Android and iOS devices. This is largely frictionless for the user (beyond the network connection), as the Messages API uses a mix of radios (Bluetooth, Bluetooth LE, WiFi) and ultrasonic signaling to handle the pairing and interaction.

The Nearby Connections API offers connection-based group messaging between devices on the same WiFi network. While you can pass more data this way, since everybody has to be on WiFi, it reduces the number of potential communications partners.

While some aspects of Google Nearby (e.g., ultrasound) are unusual, there have been many projects offering server-less group communications, from ZeroMQ to AllJoyn.

SafetyNet

The SafetyNet APIs lets your app know “whether the device where it is running matches the profile of a device that has passed Android compatibility testing”. Presumably, this is designed to help you detect custom ROMs or copies of your app installed from pirate sites onto incompatible hardware.

Adding Play Services to Your Project

On the surface, using Play Services should be simple: add the aforementioned implementation statement(s), then start calling some methods from the supplied Play Services SDK libraries.

Unfortunately, it is not that simple. There are a number of other things that you will need to deal with in order to integrate Play Services into your app.

The Metadata

You will see plenty of examples that show having a <meta-data> element, inside your <application> element, with an android:name of com.google.android.gms.version and a value pulling in an integer resource (@integer/google_play_services_version) from the Play Services SDK:

  <application
    android:allowBackup="false"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/Theme.Apptheme">
    <activity android:name=".WeatherDemo">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>

        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>
    <activity android:name=".LegalNoticesActivity"/>

    <meta-data
      android:name="com.google.android.gms.version"
      android:value="@integer/google_play_services_version"/>
  </application>
(from Location/FusedNew/app/src/main/AndroidManifest.xml)

This is no longer required, if you are using version 8.1.0 or higher of the Play Services SDK. This element will be added to your manifest automatically via the manifest merger process.

Dealing with Runtime Permissions

Android 6.0’s runtime permission system affects some of the Play Services APIs. For example, if you are trying to get the location via the fused location provider, you will need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION. Both of those are dangerous permissions, so apps with a targetSdkVersion of 23 or higher will need to request those permissions at runtime.

The Location/FusedNew sample application contains an AbstractGoogleApiClientActivity that, among other things, helps us deal with runtime permissions in our Play Services SDK-using apps.

Detecting If We Have Permission

The idea behind AbstractGoogleApiClientActivity is that apps using the Play Services SDK will have activities that inherit from AbstractGoogleApiClientActivity, overriding a few methods to configure how AbstractGoogleApiClientActivity handles things like runtime permissions. For example, AbstractGoogleApiClientActivity has an abstract method named getDesiredPermissions() that subclasses must override, providing a String array of permissions that the activity needs. AbstractGoogleApiClientActivity then uses hasAllPermissions() and hasPermission() private methods to determine whether all of the requested permissions are currently held:

  private boolean hasAllPermissions(String[] perms) {
    for (String perm : perms) {
      if (!hasPermission(perm)) {
        return(false);
      }
    }

    return(true);
  }

  private boolean hasPermission(String perm) {
    return(ContextCompat.checkSelfPermission(this, perm)==
      PackageManager.PERMISSION_GRANTED);
  }
(from Location/FusedNew/app/src/main/java/com/commonsware/android/weather2/AbstractGoogleApiClientActivity.java)

In onCreate() of AbstractGoogleApiClientActivity, among other things, we call hasAllPermissions() to see if we have all of our required permissions — if yes, we can go ahead and call an initPlayServices() method to start the process of initializing our access to the Play Services SDK:

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (savedInstanceState!=null) {
      isInPermission=
        savedInstanceState.getBoolean(STATE_IN_PERMISSION, false);
      isResolvingPlayServicesError=
        savedInstanceState.getBoolean(STATE_IN_RESOLUTION, false);
    }

    if (hasAllPermissions(getDesiredPermissions())) {
      initPlayServices();
    }
    else if (!isInPermission) {
      isInPermission=true;

      ActivityCompat
        .requestPermissions(this,
          netPermissions(getDesiredPermissions()),
          REQUEST_PERMISSION);
    }
(from Location/FusedNew/app/src/main/java/com/commonsware/android/weather2/AbstractGoogleApiClientActivity.java)

Requesting Permissions

If we do not have all of the permissions, onCreate() will call requestPermissions() on ActivityCompat to ask the user for them. However, it also leverages netPermissions() to filter out the permissions that the user previously granted, so we only bother the user with permissions that either the user has not seen before or has previously denied:

  private String[] netPermissions(String[] wanted) {
    ArrayList<String> result=new ArrayList<String>();

    for (String perm : wanted) {
      if (!hasPermission(perm)) {
        result.add(perm);
      }
    }

    return(result.toArray(new String[result.size()]));
  }
(from Location/FusedNew/app/src/main/java/com/commonsware/android/weather2/AbstractGoogleApiClientActivity.java)

Note that this code is not making use of shouldShowRequestPermissionRationale(), to detect previous permission denials and perhaps show some UI to educate the user on what the impacts are of this rejection.

Handling the Result

The call to requestPermissions() will eventually trigger a callback to onRequestPermissionsResult(). Here, if we now have all of the permissions, we call initPlayServices() (more on this in a bit) and then connect() to the Play Services SDK (also, more on this in a bit):

  @Override
  public void onRequestPermissionsResult(int requestCode,
                                         String[] permissions,
                                         int[] grantResults) {
    isInPermission=false;

    if (requestCode==REQUEST_PERMISSION) {
      if (hasAllPermissions(getDesiredPermissions())) {
        initPlayServices();
        playServices.connect();
      }
      else {
        handlePermissionDenied();
      }
    }
  }
(from Location/FusedNew/app/src/main/java/com/commonsware/android/weather2/AbstractGoogleApiClientActivity.java)

If, however, we do not have all of the requested permissions, another abstract method on this class is handlePermissionDenied(), where the subclass can do what it wants to. That could range from explaining to the user what can and cannot be done to simply calling finish() and going away.

Dealing with Configuration Changes

There is a possibility that the user will rotate the screen or otherwise trigger a configuration change while we are in the request-permission process. Even though our activity is not in the foreground from an input standpoint, it is visible, and so it will undergo the configuration change while the request-permission dialog is still in the foreground. We do not want to pop up the dialog again (and confuse the user). So, the isInPermission field is tracking whether the request-permission dialog is outstanding, so we do not attempt to show the dialog again in onCreate().

Since the activity could be destroyed and recreated as part of the configuration change, we hang onto the isInPermission value in the saved instance state Bundle:

  @Override
  protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    outState.putBoolean(STATE_IN_PERMISSION, isInPermission);
    outState.putBoolean(STATE_IN_RESOLUTION,
      isResolvingPlayServicesError);
  }
(from Location/FusedNew/app/src/main/java/com/commonsware/android/weather2/AbstractGoogleApiClientActivity.java)

(the STATE_IN_RESOLUTION bit will be explained shortly)

And, in onCreate(), we re-initialize isInPermission if we got the saved instance state Bundle passed in.

Checking for Play Services

While you typically think of Android devices as having Play Services, that is not always the case. Sometimes, they do not, yet wind up having a copy of your app anyway, perhaps through less-than-legal measures. Or, the device has Play Services, but it is not the latest version — perhaps the user missed a recent Play Services update due to international travel, taking up temporary residence in a Faraday cage, or other technical issues.

Hence, another thing that AbstractGoogleApiClientActivity does is confirm that the device has Play Services and can connect to the Play Services process for whatever particular API(s) we wish to use.

Initializing the GoogleApiClient

For many, though not all, Play Services APIs, you use a GoogleApiClient as your entry point for talking to Play Services. Some APIs, like Maps V2, do not use GoogleApiClient for some reason. But, more often than not, you will find yourself needing GoogleApiClient.

To create a GoogleApiClient instance, use a GoogleApiClient.Builder. As the class name suggests, GoogleApiClient is used as a client connection to many (but not all) Google Play Services APIs, and GoogleApiClient.Builder is a builder for building such a connection. In particular:

This is handled by initPlayServices() on AbstractGoogleApiClientActivity, which we call once we have our permissions set up:

  protected void initPlayServices() {
    playServices=
      configureApiClientBuilder(new GoogleApiClient.Builder(this))
        .addConnectionCallbacks(this)
        .addOnConnectionFailedListener(this)
        .build();
  }
(from Location/FusedNew/app/src/main/java/com/commonsware/android/weather2/AbstractGoogleApiClientActivity.java)

This includes calling out to the subclass’ implementation of configureApiClientBuilder(), where the subclass can use methods like addApi() to indicate specifically what parts of the Play Services family of APIs the activity wants to use.

Given that we have a GoogleApiClient, we need to connect() to it to be able to start requesting location data, then disconnect() from it when we no longer need that location data.

Disconnecting is easy: we do that in onStop() of AbstractGoogleApiClientActivity:

  @Override
  protected void onStop() {
    if (playServices!=null) {
      playServices.disconnect();
    }

    super.onStop();
  }
(from Location/FusedNew/app/src/main/java/com/commonsware/android/weather2/AbstractGoogleApiClientActivity.java)

There are two places where we possibly call connect(). One is if we needed to ask for permissions, and the user granted them. In onRequestPermissionsResult(), after confirming that we do indeed have all necessary permissions, we call initPlayServices() and then immediately call connect() on the GoogleApiClient:

  @Override
  public void onRequestPermissionsResult(int requestCode,
                                         String[] permissions,
                                         int[] grantResults) {
    isInPermission=false;

    if (requestCode==REQUEST_PERMISSION) {
      if (hasAllPermissions(getDesiredPermissions())) {
        initPlayServices();
        playServices.connect();
      }
      else {
        handlePermissionDenied();
      }
    }
  }
(from Location/FusedNew/app/src/main/java/com/commonsware/android/weather2/AbstractGoogleApiClientActivity.java)

If we did not need to request permissions, we call connect() in onStart(), mirroring the onStop() where we are disconnecting:

  @Override
  protected void onStart() {
    super.onStart();

    if (!isResolvingPlayServicesError && playServices!=null) {
      playServices.connect();
    }
  }
(from Location/FusedNew/app/src/main/java/com/commonsware/android/weather2/AbstractGoogleApiClientActivity.java)

The isResolvingPlayServicesError boolean value will be discussed a bit later in this chapter.

Connecting and Disconnecting

The call to connect(), in turn, will trigger calls to our onConnected() and onDisconnected() methods of the GoogleApiClient.ConnectionCallbacks interface, assuming all goes well. AbstractGoogleApiClientActivity does not provide those implementations; they are considered part of the abstract API and therefore need to be implemented by subclasses.

However, apparently it is possible for this connection attempt to fail. Exactly how and why it might fail is not well documented. If it fails, the onConnectionFailed() method from our GoogleApiClient.OnConnectionFailedListener implementation will be called. onConnectionFailed() is passed a ConnectionResult indicating what specifically went wrong.

It turns out that this ConnectionResult may contain a PendingIntent that can be used to try to help the user recover from whatever the problem was. The recipe that we have been given to try to use this is to call hasResolution() (to see if the PendingIntent exists) and to use startResolutionForResult() (to invoke the activity pointed to by the PendingIntent). Of course, hasResolution() may return false, and apparently the PendingIntent might be broken, so we have to handle those scenarios as well:

  @Override
  public void onConnectionFailed(ConnectionResult result) {
    if (!isResolvingPlayServicesError) {
      if (result.hasResolution()) {
        try {
          isResolvingPlayServicesError=true;
          result.startResolutionForResult(this, REQUEST_RESOLUTION);
        }
        catch (IntentSender.SendIntentException e) {
          playServices.connect();
        }
      }
      else {
        ErrorDialogFragment.newInstance(result.getErrorCode())
          .show(getFragmentManager(),
            TAG_ERROR_DIALOG_FRAGMENT);
        isResolvingPlayServicesError=true;
      }
    }
  }
(from Location/FusedNew/app/src/main/java/com/commonsware/android/weather2/AbstractGoogleApiClientActivity.java)

If we have a resolution and successfully start up the resolution activity, our activity will be stopped and later started, at which point we will wind up trying to connect() again naturally.

If there is no PendingIntent to try to resolve the problem, we can still attempt to display a dialog with information about what is going wrong. The Play Services SDK provides this dialog, though we are responsible for wrapping it in a DialogFragment ourselves. That comes in the form of ErrorDialogFragment:

  public static class ErrorDialogFragment extends
    DialogFragment {
    static final String ARG_ERROR_CODE="errorCode";

    static ErrorDialogFragment newInstance(int errorCode) {
      Bundle args=new Bundle();
      ErrorDialogFragment result=new ErrorDialogFragment();

      args.putInt(ARG_ERROR_CODE, errorCode);
      result.setArguments(args);

      return(result);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
      return(GoogleApiAvailability
        .getInstance()
        .getErrorDialog(
          getActivity(),
          getArguments().getInt(ARG_ERROR_CODE),
                REQUEST_RESOLUTION));
    }

    @Override
    public void onCancel(DialogInterface dlg) {
      if (getActivity()!=null) {
        getActivity().finish();
      }

      super.onCancel(dlg);
    }

    @Override
    public void onDismiss(DialogInterface dlg) {
      if (getActivity()!=null) {
        ((AbstractGoogleApiClientActivity)getActivity())
          .isResolvingPlayServicesError=false;
      }

      super.onDismiss(dlg);
    }
  }
(from Location/FusedNew/app/src/main/java/com/commonsware/android/weather2/AbstractGoogleApiClientActivity.java)

onCreateDialog() uses the GoogleApiAvailability singleton to show the error dialog, given the error code that came from our previous attempt to connect. We pass that error code over to the ErrorDialogFragment via the arguments Bundle, so that it can survive a configuration change.

However, we also have to take into account that the device might undergo a configuration change while either the resolution activity started by startActivityForResult() or the ErrorFragmentDialog is in the foreground. What we do not want to do is immediately try connecting to Play Services again in onStart(), while we are in the process of trying to fix whatever problem prevented us from connecting to it previously.

So, we have to track a boolean state, isResolvingPlayServicesError, as a field in our activity. That is initially set to false, but we flip it to true if we show the resolution activity or the ErrorFragmentDialog. We flip it back to false when either the started activity returns control to us in onActivityResult() or when the ErrorDialogFragment is dismissed. While that flag is true, we skip attempting to connect to Play Services in onStart(). And this flag is part of our saved instance state, so we can handle configuration changes.