360 andev 2017 ty smith header

Deep Android Integrations

If you enjoy talks from 360 AnDev, please support the conference via Patreon!

Designing an elegant interface for developers to communicate with your Android app is crucial for building a mobile platform. In this talk, we’ll walk through many of the best practices that he has accumulated and you’ll find out how to allow third party developers to seamlessly interact with your users’ local data to shortcut more expensive server operations. Topics will include building single sign-on, surfacing your local databases, constructing well-defined Intent interfaces, using deep links, and binding services for programmatic communication. We will walk through how to create a well-defined interface in your app – if that’s something you want to know, don’t miss it!


Introduction

My name is Ty, and I’ve been working on various aspects of mobile development platforms for the last four years. I started in this niche at Evernote in 2012. I continued while at Twitter working on the Fabric tools, and now I’m contributing to the developer platform at Uber.

The Traditional Approach

A traditional approach to a mobile integration against a web service may have a variety of apps on the device. Each one may have an SDK, and the SDK is talking to an independent web service.

This approach has many benefits, such as more flexibility and the guarantee of the existence of the third-party code inside every app, but it has a downside. Every app bundles the same code making independent network requests and caching duplicate data.

A . more interesting approach is to provide functionality that already exists in a first-party app to subsequent third-party devices that may be installed.

Example

Let’s start with a recent project that I was working on: bringing a consistent deep link framework into the rewrite of the Uber Rider app. For those of you not very familiar with deep links, it’s a general term used on Android and iOS to represent a URI that an application can handle for a specific intent from the user. An application will initiate the URI and it starts the ride request flow in the Uber app.

The URI is just made of a series of parts that communicate the intention of the deep link. The query parameters here are the information that is needed for the ride. In this case, the pickup location.

By registering the URI information in our Android manifest, we’re telling the Android system that our app can handle this URI, and that when the system throws it, to alert us and allow us an opportunity to handle it.

Get more development news like this

	<activity
		android:name=".RideRequestActivity">
		<intent-filter>
			<action android:name="android.intent.action.VIEW"/>
			<category android:name="android.ingtent.category.DEFAULT"/>
			<category android:name="android.intent.category.BROWSABLE"/>
			<data android:scheme="uber" android:authority="rideRequest"/>
		</intent-filter>
	</activity>

Here, we’re specifying the scheme and the authority in the intent filter, so only the URI that matches our exact requirements will start the request, or start the activity for the specific intention.

*What would happen when the application isn’t installed on the device? *

Because we’re using a standard URL, the browser will handle opening this, but this is a great opportunity for you to provide the best possible user experience to get the user to install the app, and then engage in the specific intention that they had.

A way of approaching the solution is to fingerprint the device so that you have a unique identifier representing the intention, then forward the user onto either the App Store or the Play store, depending on the platform, with that information.

Once the user is on the Play Store and they install the application and launch it for the first time, the Play store will emit a broadcast that your app can register to receive, called the “Install Vending Broadcast.” Inside that intent, it will have the reference information that you originally passed in as the query parameter to the Play Store.

The app now has the original deep link that the user clicked before the app was installed. You can continue them in the flow. With Android Marshmallow 23 and up, the application can be set by default to handle URL.

To implement app linking, you just need to make two modifications to the existing deep link code that we showed.

  • Add an auto verify tag and set it to true on the manifest. This tells the system for anything that’s declared here to automatically try to verify that you’re the default handler for this.

  • Create an assetslinks.json file that exists on your domain. For example, if we want to handle www.uber.com, we would need a assetslinks.json file existing on the host for uber.com. The content of that would be your public KeyStore fingerprint for the APK installed on the device. The Android system checks to see if the signatures match before opening the app by default.

While deep links can be very powerful, they can also spawn their own set of unique problems and bugs.

In our location deep link, we’ve specified lat, long from the method locale.getDefault. In some locales, a comma is used instead of a decimal for indicating a percentage of a number.

However, it was expecting a comma, saw the period, and the number formatted tried did not throw an exception, and grabbed the last chunk of usable information off of each one of the strings. This resulted in incorrect geolocation information. The fix for this was to switch it to Locale.us.

Authentication

OAuth

A common mobile developer platform feature that we’ve built and used across all platforms is to integrate authentication into the app to call certain endpoints.

A traditional OAuth is three-legged where the mobile client has a client ID, and the server has a client secret. Only after the client logs in through a web view will it get an authorization code. When the client sends it to the server, the server combines it with its secret token, it returns an access token that allows the client to call the endpoints.

This is a bit of overhead, and it has been improved upon significantly. By integrating the authentication process against the first-party app on the device, as opposed to trying to do it through a web view where you have to log in, you can guarantee the integrity of the device because that first-party app itself can act as that second factor to that server.

In this more simplified process, a person implementing a consumer app would only need to start the flow with their client ID. Here, the Uber app intercepts the client ID through deep linking. Then loads up the additional information that’s needed, hits the Uber server, and authorizes the user. The user does not need to log in.

Single Sign On

There are three approaches:

1) Have the third-party app call the URI that the provider app can handle, and the provider app calls a URI to redirect back upon completion.

2) Use custom actions, a bundle, and an intent to initiate the flow and start activity for results. Then on the activity results, parse out the pertinent information, and get the key from that.

3) Use the Android Account Manager to manage flows. That gives you the standard way of approaching this implementation on Android. This is, however, only unique to Android.

Using the URI’s is the most straightforward approach, as it works for both platforms.

The client app starts a URI that is registered by the provider app it can handle. If not, then it follows the browser auth flow. Once the user had granted approval on either one of these flows, which, at this point, is agnostic to the end user, we follow that redirected URI.


	Intent intent = new Intent(Intent.ACTION_VIEW);
	final Uri deepLinkUri = createSsoUri();
	intent.setData(deepLinkUri);
	intent.setPackage("com.ubercab");
	activity.startActivityForResult(intent, requestCode);
  

If the consuming app has registered for that in their manifest, it will launch back into their application, and they’re able to parse out the access token from the query parameters.

I recommend that on both sides of this integration, you set the package names to make sure that you’re being very specific on the app that you’re trying to talk to. If I had two apps installed on the device, and both of them could handle the same URI, it might put the user in a confusing spot when they see the intent picker launch


	PackageInfor packageInfo = packageManager.getPackageInfo(
		packageName, PackageManager.GET_SIGNATURES);

	for (Signature signature : packageInfo.signatures) {
			String hashedSignature = Utility.sha1hash(signature.toByteArray());
			if (validAppSignatureHashes.contains(hashedSignature)) {
				return true
			}
			return false;
	}

To continue to mitigate security flaws, you should validate the signature of your connecting app, and make sure that it matches what’s expected from the public key.

In our code above, GET_SIGNATURES, provides an array of signatures. Those signatures are the public signatures for the Root CA, Intermediate CA and Uber SSL Cert CA’s.

It’s easy to generate a trust chain, and there is a known security vulnerability, called the fake ID vulnerability. It’s possible to write a script that takes the public key from the cert, that generates a fake CA.

Using this logic, I was able to generate a fake app that intercepted the auth requests for many different open SDKs today, everything from Facebook to Dropbox to Coinbase to Microsoft Azure.

This is no longer a problem, because of Google Play services updates, as well as PRs and bug bounties.

Here is the implementation for the code of how it should operate.

PackageInfo packageInfo = packageManager.getPackageInfo(
		packageName, PackageManager.GET_SIGNATURES);

for (Signature signature : packageInfo.signatures) {
	String hashedSignature = Utility.sha1hash(signature.toByteArray());
	if (!validAppSignatureHashes.contains(hashedSignature)) {
		//Invalid Signature
	}
}
//Valid signature

Inter-process Communication

Samsung has a personal assistant called Bixby that’s a competitor to Siri and Google Voice. Implementing this required back-and-forth communication with the Uber Service.

A solution was to use both a mixture of the Web API and deep links/IPC to communicate between Bixby and the Uber app.

Instead of building one API to talk to each feature that was needed, we built a generic pipeline that the consumer can make a request. This manages the authorization token for the user, and it allows the app to stay stable because it’s not opinionated on specific APIs.

How does IPC work, exactly, on Android?

First, define an Android Interface Definition Language. This is much like an interface, but it uses an AIDL extension and it auto generates the code. Then implement what’s needed to use this to communicate across apps or across processes.

Here is a lightweight AIDL class that can receive a GET request:

	interface UberApi {
		Bundle get(in String clientId, in Uri uri);
	}
  

Implement the auto generated stub for the AIDL file.

public class UberApiBinder implements UberApiService.Stub() {
	public Bundle get(String clientId, Uri uri) {
		if (validateClientId(clientId)){
			return externalApiService.get(uri);
		} else {
			return new Bundle();
		}
	}
}

Create a service that, on binding, returns the API binder that we defined.

	public class UberApiService extends Service {
		@Override
		public IBinder onBind(Intent intent) {
			return new UberApiBinder();
		}
	}

Finally, consume the remote service in another app by setting up a service connection. It receives callbacks for connected and disconnected states so we know when calls can be made on the service.

	private UberApiService service = null;
	private ServiceConnection connection = new ServiceConnection() {
		public void onServiceConnected(ComponentName className, IBinder service) {
			service = UberApiService.Stub.asInterface(service);
			//Can now safely make requests with service
		}

		public void onServiceDisconnected( ComponentName className) {
			service = null;
		}
	}
	public void onResume() {
		bindService(new Intent(this, UberApiService.class), connection, Context,BIND_AUTOCREATE);
	}
	public void onPause() {
		unbindService(connection);
		service = null;
	}

The App as a Platform

We need to think about providing the app itself as a platform. At Evernote, we modeled the platform after the standard CRUD, and provided a comfortable experience for developers. We did this for many of the different model types, like notes, notebooks, and users.

For example, if the list intent is used, we would show the UI element for the list, but if new was used, it would open the composer with the draft, or potentially any information they had sent over that you wanted to pre-fill. Here’s an example of how the caller would call to engage to start a new note with that intent API.


	public void newNoteWithContent(Uri image) {
		Intent intent = new Intent();
		intent.setAction(NEW_NOTE);

		intent.putExtra(Intent.EXTRA_TITLE, "LET ME EXPLAIN YOU INTENTS");
		intent.putExtra(Intent.EXTRA_TEXT, "BLAH");

		intent.setData(image);
		startActivityForResult(intent);
	}

All they need to do is set the custom action. In the intent, they could put in the information that we needed to parse out.

Authenticator

Extend two classes that Android provides: -

  • AbstractAccountAuthenticator - a service that the account manager communicates with.
  • AccountAuthenticatorActivity - an activity to handle the login or signup flow.

The process begins by trying to get an account token, and on success, a callback is called, which contains a key intent if available. The key intent contains information on how to start the authenticator activity to finish the process. If it doesn’t, it’ll contain the token information directly in the bundle to be parsed out and used. On error, those will be reported back with the error callback.

	@Override
	public Bundle addAccount(AccountAuthenticatorResponse response,
		String accountType, String authTokenType, String[] requiredFeatures, Bundle options)
		throws NetwordErrorException {
		
		final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
		intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, accountType);
		intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);
		intent.putExtra(AuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT, true);
		intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
		final Bundle bundle = new Bundle();
		bundle.putParcelable(AccountManager.KEY_INTENT, intent);
		return bundle;
	}

In the provider, you need to implement the AddAccount method in the abstract account authenticator. It’s called when the user wants to add an account for your application.

You would need to implement a login or signup flow, which is called directly by the account manager. Then we can set the activity someone specific.

Conclusion

Although it’s rewarding to build these systems, debugging them can be really challenging. I’ll give you a couple quick tips before we end it today.

In my experience, many of these integrations are device-specific. You may be building against specific cameras on individual manufacturers or specific products. You need really good logging to help you with this.

I recommend building mock integrations for how you expect your integrations to be consumed that you can automate testing.

Lastly, rely on great tools to get information from the wild - Crash Analytics for Firebase.

Questions

How do you handle security for the integrations to make sure a malware app that poses as a game isn’t just sucking up your Evernotes?

You can define permission levels, permission groups and custom permissions on most of these components. You can define those either programmatically or in your XML manifest. For most of these, we define custom permissions that the calling app would have to implement in their manifest, or if it was on later versions of Android, they’d have to prompt the user for run time permission to access that.

If I wanted to integrate with a popular messaging app, how would I go about finding what interfaces they’ve implemented?

There’s not a standard way to get those at the moment. The best way is to find their developer documentation.

Next Up: Android Architecture Components and Realm

General link arrow white

About the content

This talk was delivered live in July 2017 at 360 AnDev. The video was recorded, produced, and transcribed by Realm, and is published here with the permission of the conference organizers.

Ty Smith

Ty has been working on Android since 2009. He is a tech lead at Uber, focusing on the external developer platform. He is a member of the Google Developer Expert program and regularly speaks at international conferences on Android. He organizes the SF Android Meetup group and Droidcon SF. He is a member of the technical advisory and investment group, Specialized Types. Prior to Uber, Ty worked on the Fabric tools at Twitter, the Evernote Android App and SDK, a messaging platform for Sprint, and Zagat for Android.

4 design patterns for a RESTless mobile integration »

close