Deep Linking for Android

Deep Linking provides a mechanism to open an application to a specific resource. The Urban Airship Action framework supports the opening of deep links via the payload of a notification, a link from a Rich App Page or the JavaScript Interface. The Urban Airship library only provides ways to trigger deep links; the application still needs to be set up to handle deep linking.

Default Deep Link Strategy

The default deep link action assumes that the application is set up to handle deep links through Android implicit intents. Implicit intents are intents used to launch components by specifying an action, category, and data instead of providing the component’s class. This allows any application to invoke another application’s component to perform an action. Only application components that are configured to receive the intent, by specifying an intent filter, will be considered.

In this example, a single activity will be declared to handle all deep links for the app with the scheme vnd.urbanairship.sample and host deeplink. The activity will only exist long enough to open the deep link and will never be visible to the user. A complete example can be found in the Sample Application.

When selecting a scheme for deep linking, choose one unique enough so as not to conflict with other apps on the user’s device, in order to avoid showing the user an app chooser.

Create the activity that parses the deep link:

public class ParseDeepLinkActivity extends Activity {
    public static final String PREFERENCE_DEEP_LINK = "/home/preferences";
    public static final String INBOX_DEEP_LINK = "/inbox/messages";

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

        Intent intent = getIntent();
        if (intent == null || intent.getData() == null) {
            finish();
        }

        openDeepLink(intent.getData());

        // Finish this activity
        finish();
    }

    private void openDeepLink(Uri deepLink) {
        String path = deepLink.getPath();

        if (PREFERENCE_DEEP_LINK.equals(path)) {
            // Launch preferences
            startActivity(new Intent(this, PushPreferencesActivity.class));
        } else if (INBOX_DEEP_LINK.equals(path)) {
            // Launch the inbox activity
            startActivity(new Intent(this, InboxActivity.class));
        } else {
            // Fall back to the main activity
            startActivity(new Intent(this, MainActivity.class));
        }
    }
}

Declare the activity in the AndroidManifest.xml file with the intent filter. The intent filter should accept the default intent action android.intent.action.VIEW and either android.intent.category.DEFAULT or android.intent.category.BROWSABLE category. It should also filter out any intents that do not contain a data URI with the scheme vnd.urbanairship.sample and host deeplink.

   <activity android:name=".ParseDeepLinkActivity">
       <intent-filter>
           <action android:name="android.intent.action.VIEW"/>

           <!-- Handles any vnd.urbanairship.sample://deeplink URI's -->
           <data android:scheme="vnd.urbanairship.sample" android:host="deeplink" />

           <category android:name="android.intent.category.DEFAULT"/>
           <category android:name="android.intent.category.BROWSABLE"/>
       </intent-filter>
</activity>

The application is now able to parse any predefined deep link paths. Alternatively, the ParseDeepLinkActivity could parse each segment of the path to construct a task stack using the Android TaskStackBuilder. This will enable sending any combination of deep links to define the entire task stack.

private void openDeepLink(Uri deepLink) {
    TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);

    // Treat each path segment of the URI as a deep link
    List<String> segments = deepLink.getPathSegments();

    // Create a task stack from the deep links
    if (segments != null) {
        for (String segment : segments) {
            Intent route = null;
            if ("preferences".equals(segment)) {
                route = new Intent(getApplicationContext(), PushPreferencesActivity.class);
            } else if ("inbox".equals(segment)) {
                route = new Intent(getApplicationContext(), InboxActivity.class);
            } else if ("home".equals(segment)) {
                route = new Intent(getApplicationContext(), MainActivity.class);
            }

            if (route != null) {
                stackBuilder.addNextIntentWithParentStack(route);
            }
        }
    }

    // Fall back to the main activity
    if (stackBuilder.getIntentCount() == 0) {
        stackBuilder.addNextIntent(new Intent(this, MainActivity.class));
    }

    // Launch the activities
    stackBuilder.startActivities();
}

The deep link’s URI parameters can be used to pass extra data to one or more of the activities that are being invoked.

private Bundle parseOptions(Uri deepLink) {
    Bundle options = new Bundle();
    Map<String, List<String>> queryParameters = UriUtils.getQueryParameters(deepLink);

    if (queryParameters == null) {
        return options;
    }

    for (String key : queryParameters.keySet()) {
        List<String> values = queryParameters.get(key);
        if (values.size() == 1) {
            options.putString(key, values.get(0));
        } else if (values.size() > 1) {
            options.putStringArrayList(key, new ArrayList<String>(values));
        }
    }

    return options;
}

Before launching the activities in the task stack, set the extras on the last intent:

Bundle extras = parseOptions(deepLink);
if (extras != null) {
    // Add the extras to the last intent
    stackBuilder.editIntentAt(stackBuilder.getIntentCount() - 1).putExtras(extras);
}

// Launch the activities
stackBuilder.startActivities();

The app is now able to handle any deep links in the form of vnd.urbanairship.sample://deeplink/*. Where * is replaced with any deep link segments that the application knows how to parse and it will build the task stack in the specified order.

Example:

vnd.urbanairship.sample://deeplink/home/inbox?id=3 will launch the inbox activity on top of the main activity with the extra “id” with value “3”.

Alternative Deep Link Strategy

If the implicit intent deep linking strategy does not work for the application, the deep link action can be overridden to provide any custom deep linking behavior.

Create a custom action that handles deep linking. The example assumes the deep link is still a URI, but any value that translates to a deep link can be used.

public class CustomDeepLinkAction extends Action {

    @Override
    public ActionResult perform(ActionArguments arguments) {
        Uri uri = UriUtils.parse(arguments.getValue().getString());

        String path = uri.getPath();
        Context context = UAirship.shared().getApplicationContext();

        Intent intent;
        if ("/home/preferences".equals(path)) {
            // Launch preferences
            intent = new Intent(context, PushPreferencesActivity.class);
        } else {
            // Fall back to the main activity
            intent = new Intent(context, MainActivity.class);
        }

        // Launch the activity
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);

        return ActionResult.newEmptyResult();
    }

    @Override
    public boolean acceptsArguments(ActionArguments arguments) {
        // Do any argument validation here.

        // Only accept String arguments
        if (arguments.getValue().getString() != null) {
            // Make sure we can parse the String as a Uri
            return UriUtils.parse(arguments.getValue().getString()) != null;
        }

        return false;
    }
}

Update the deep link action in the action registry after takeOff:

CustomDeepLinkAction action = new CustomDeepLinkAction();

ActionRegistry registry = UAirship.shared().getActionRegistry();
registry.getEntry(DeepLinkAction.DEFAULT_REGISTRY_NAME).setDefaultAction(action);