Android Open Source - Bolts-Android App Link Navigation






From Project

Back to project page Bolts-Android.

License

The source code is released under:

BSD License For Bolts software Copyright (c) 2013, Facebook, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that t...

If you think the Android project Bolts-Android listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 *  Copyright (c) 2014, Facebook, Inc./*w  ww .  j a v  a 2  s .  c  om*/
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */
package bolts;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.util.SparseArray;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Represents a pending request to navigate to an App Link. Most developers will simply use
 * {@link #navigateInBackground(android.content.Context, android.net.Uri)} to open a URL, but
 * developers can build custom requests with additional navigation and app data attached to them
 * by creating AppLinkNavigations themselves.
 */
public class AppLinkNavigation {

  private static final String KEY_NAME_USER_AGENT = "user_agent";
  private static final String KEY_NAME_VERSION = "version";
  private static final String KEY_NAME_REFERER_APP_LINK = "referer_app_link";
  private static final String KEY_NAME_REFERER_APP_LINK_APP_NAME = "app_name";
  private static final String KEY_NAME_REFERER_APP_LINK_PACKAGE = "package";
  private static final String VERSION = "1.0";

  private static AppLinkResolver defaultResolver;

  /**
   * The result of calling {@link #navigate(android.content.Context)} on an
   * {@link bolts.AppLinkNavigation}.
   */
  public static enum NavigationResult {
    /**
     * Indicates that the navigation failed and no app was opened.
     */
    FAILED("failed", false),
    /**
     * Indicates that the navigation succeeded by opening the URL in the browser.
     */
    WEB("web", true),
    /**
     * Indicates that the navigation succeeded by opening the URL in an app on the device.
     */
    APP("app", true);

    private String code;
    private boolean succeeded;
    public String getCode() {
      return code;
    }
    public boolean isSucceeded() {
      return succeeded;
    }
    NavigationResult(String code, boolean success) {
      this.code = code;
      this.succeeded = success;
    }
  }

  private final AppLink appLink;
  private final Bundle extras;
  private final Bundle appLinkData;

  /**
   * Creates an AppLinkNavigation with the given link, extras, and App Link data.
   *
   * @param appLink     the AppLink being navigated to.
   * @param extras      the extras to include in the App Link navigation.
   * @param appLinkData additional App Link data for the navigation.
   */
  public AppLinkNavigation(AppLink appLink, Bundle extras, Bundle appLinkData) {
    if (appLink == null) {
      throw new IllegalArgumentException("appLink must not be null.");
    }
    if (extras == null) {
      extras = new Bundle();
    }
    if (appLinkData == null) {
      appLinkData = new Bundle();
    }
    this.appLink = appLink;
    this.extras = extras;
    this.appLinkData = appLinkData;
  }

  /**
   * @return the App Link to navigate to.
   */
  public AppLink getAppLink() {
    return appLink;
  }

  /**
   * Gets the al_applink_data for the AppLinkNavigation. This will generally contain data common
   * to navigation attempts such as back-links, user agents, and other information that may be used
   * in routing and handling an App Link request.
   *
   * @return the App Link data.
   */
  public Bundle getAppLinkData() {
    return appLinkData;
  }

  /**
   * The extras for the AppLinkNavigation. This will generally contain application-specific data
   * that should be passed along with the request, such as advertiser or affiliate IDs or other such
   * metadata relevant on this device.
   *
   * @return the extras for the AppLinkNavigation.
   */
  public Bundle getExtras() {
    return extras;
  }

  /**
   * Creates a bundle containing the final, constructed App Link data to be used in navigation.
   */
  private Bundle buildAppLinkDataForNavigation(Context context) {
    Bundle data = new Bundle();
    Bundle refererAppLinkData = new Bundle();
    if (context != null) {
      String refererAppPackage = context.getPackageName();
      if (refererAppPackage != null) {
        refererAppLinkData.putString(KEY_NAME_REFERER_APP_LINK_PACKAGE, refererAppPackage);
      }
      ApplicationInfo appInfo = context.getApplicationInfo();
      if (appInfo != null) {
        String refererAppName = context.getString(appInfo.labelRes);
        if (refererAppName != null) {
          refererAppLinkData.putString(KEY_NAME_REFERER_APP_LINK_APP_NAME, refererAppName);
        }
      }
    }
    data.putAll(getAppLinkData());
    data.putString(AppLinks.KEY_NAME_TARGET, getAppLink().getSourceUrl().toString());
    data.putString(KEY_NAME_VERSION, VERSION);
    data.putString(KEY_NAME_USER_AGENT, "Bolts Android " + Bolts.VERSION);
    data.putBundle(KEY_NAME_REFERER_APP_LINK, refererAppLinkData);
    data.putBundle(AppLinks.KEY_NAME_EXTRAS, getExtras());
    return data;
  }

  /**
   * Gets a JSONObject-compatible value for the given object.
   */
  private Object getJSONValue(Object value) throws JSONException {
    if (value instanceof Bundle) {
      return getJSONForBundle((Bundle) value);
    } else if (value instanceof CharSequence) {
      return value.toString();
    } else if (value instanceof List) {
      JSONArray array = new JSONArray();
      for (Object listValue : (List<?>) value) {
        array.put(getJSONValue(listValue));
      }
      return array;
    } else if (value instanceof SparseArray) {
      JSONArray array = new JSONArray();
      SparseArray<?> sparseValue = (SparseArray<?>) value;
      for (int i = 0; i < sparseValue.size(); i++) {
        array.put(sparseValue.keyAt(i), getJSONValue(sparseValue.valueAt(i)));
      }
      return array;
    } else if (value instanceof Character) {
      return value.toString();
    } else if (value instanceof Boolean) {
      return value;
    } else if (value instanceof Number) {
      if (value instanceof Double || value instanceof Float) {
        return ((Number) value).doubleValue();
      } else {
        return ((Number) value).longValue();
      }
    } else if (value instanceof boolean[]) {
      JSONArray array = new JSONArray();
      for (boolean arrValue : (boolean[]) value) {
        array.put(getJSONValue(arrValue));
      }
      return array;
    } else if (value instanceof char[]) {
      JSONArray array = new JSONArray();
      for (char arrValue : (char[]) value) {
        array.put(getJSONValue(arrValue));
      }
      return array;
    } else if (value instanceof CharSequence[]) {
      JSONArray array = new JSONArray();
      for (CharSequence arrValue : (CharSequence[]) value) {
        array.put(getJSONValue(arrValue));
      }
      return array;
    } else if (value instanceof double[]) {
      JSONArray array = new JSONArray();
      for (double arrValue : (double[]) value) {
        array.put(getJSONValue(arrValue));
      }
      return array;
    } else if (value instanceof float[]) {
      JSONArray array = new JSONArray();
      for (float arrValue : (float[]) value) {
        array.put(getJSONValue(arrValue));
      }
      return array;
    } else if (value instanceof int[]) {
      JSONArray array = new JSONArray();
      for (int arrValue : (int[]) value) {
        array.put(getJSONValue(arrValue));
      }
      return array;
    } else if (value instanceof long[]) {
      JSONArray array = new JSONArray();
      for (long arrValue : (long[]) value) {
        array.put(getJSONValue(arrValue));
      }
      return array;
    } else if (value instanceof short[]) {
      JSONArray array = new JSONArray();
      for (short arrValue : (short[]) value) {
        array.put(getJSONValue(arrValue));
      }
      return array;
    } else if (value instanceof String[]) {
      JSONArray array = new JSONArray();
      for (String arrValue : (String[]) value) {
        array.put(getJSONValue(arrValue));
      }
      return array;
    }
    return null;
  }

  /**
   * Gets a JSONObject equivalent to the input bundle for use when falling back to a web navigation.
   */
  private JSONObject getJSONForBundle(Bundle bundle) throws JSONException {
    JSONObject root = new JSONObject();
    for (String key : bundle.keySet()) {
      root.put(key, getJSONValue(bundle.get(key)));
    }
    return root;
  }

  /**
   * Performs the navigation.
   *
   * @param context the Context from which the navigation should be performed.
   * @return the {@link bolts.AppLinkNavigation.NavigationResult} performed by navigating.
   */
  public NavigationResult navigate(Context context) {
    PackageManager pm = context.getPackageManager();
    Bundle finalAppLinkData = buildAppLinkDataForNavigation(context);

    Intent eligibleTargetIntent = null;
    for (AppLink.Target target : getAppLink().getTargets()) {
      Intent targetIntent = new Intent(Intent.ACTION_VIEW);
      if (target.getUrl() != null) {
        targetIntent.setData(target.getUrl());
      } else {
        targetIntent.setData(appLink.getSourceUrl());
      }
      targetIntent.setPackage(target.getPackageName());
      if (target.getClassName() != null) {
        targetIntent.setClassName(target.getPackageName(), target.getClassName());
      }
      targetIntent.putExtra(AppLinks.KEY_NAME_APPLINK_DATA, finalAppLinkData);

      ResolveInfo resolved = pm.resolveActivity(targetIntent, PackageManager.MATCH_DEFAULT_ONLY);
      if (resolved != null) {
        eligibleTargetIntent = targetIntent;
        break;
      }
    }

    Intent outIntent = null;
    NavigationResult result = NavigationResult.FAILED;
    if (eligibleTargetIntent != null) {
      outIntent = eligibleTargetIntent;
      result = NavigationResult.APP;
    } else {
      // Fall back to the web if it's available
      Uri webUrl = getAppLink().getWebUrl();
      if (webUrl != null) {
        JSONObject appLinkDataJson;
        try {
          appLinkDataJson = getJSONForBundle(finalAppLinkData);
        } catch (JSONException e) {
          sendAppLinkNavigateEventBroadcast(context, eligibleTargetIntent, NavigationResult.FAILED, e);
          throw new RuntimeException(e);
        }
        webUrl = webUrl.buildUpon()
            .appendQueryParameter(AppLinks.KEY_NAME_APPLINK_DATA, appLinkDataJson.toString())
            .build();
        outIntent = new Intent(Intent.ACTION_VIEW, webUrl);
        result = NavigationResult.WEB;
      }
    }

    sendAppLinkNavigateEventBroadcast(context, outIntent, result, null);
    if (outIntent != null) {
      context.startActivity(outIntent);
    }
    return result;
  }

  private void sendAppLinkNavigateEventBroadcast(Context context, Intent intent, NavigationResult type, JSONException e) {
    Map<String, String> extraLoggingData = new HashMap<String, String>();
    if (e != null) {
      extraLoggingData.put("error", e.getLocalizedMessage());
    }

    extraLoggingData.put("success", type.isSucceeded() ? "1" : "0");
    extraLoggingData.put("type", type.getCode());

    MeasurementEvent.sendBroadcastEvent(
        context,
        MeasurementEvent.APP_LINK_NAVIGATE_OUT_EVENT_NAME,
        intent,
        extraLoggingData);
}

  /**
   * Sets the default resolver to be used for App Link resolution. Setting this to null will cause
   * the {@link #navigateInBackground(android.content.Context, android.net.Uri)} methods to use the
   * basic, built-in resolver provided by Bolts.
   *
   * @param resolver the resolver to use by default.
   */
  public static void setDefaultResolver(AppLinkResolver resolver) {
    defaultResolver = resolver;
  }

  /**
   * Gets the default resolver to be used for App Link resolution. If the developer has not set a
   * default resolver, this will return {@code null}, but the basic, built-in resolver provided by
   * Bolts will be used.
   *
   * @return the default resolver, or {@code null} if none is set.
   */
  public static AppLinkResolver getDefaultResolver() {
    return defaultResolver;
  }

  private static AppLinkResolver getResolver(Context context) {
    if (getDefaultResolver() != null) {
      return getDefaultResolver();
    }
    return new WebViewAppLinkResolver(context);
  }

  /**
   * Navigates to an {@link bolts.AppLink}.
   *
   * @param context the Context from which the navigation should be performed.
   * @param appLink the AppLink being navigated to.
   * @return the {@link bolts.AppLinkNavigation.NavigationResult} performed by navigating.
   */
  public static NavigationResult navigate(Context context, AppLink appLink) {
    return new AppLinkNavigation(appLink, null, null).navigate(context);
  }

  /**
   * Navigates to an {@link bolts.AppLink} for the given destination using the App Link resolution
   * strategy specified.
   *
   * @param context     the Context from which the navigation should be performed.
   * @param destination the destination URL for the App Link.
   * @param resolver    the resolver to use for fetching App Link metadata.
   * @return the {@link bolts.AppLinkNavigation.NavigationResult} performed by navigating.
   */
  public static Task<NavigationResult> navigateInBackground(final Context context,
                                                            Uri destination,
                                                            AppLinkResolver resolver) {
    return resolver.getAppLinkFromUrlInBackground(destination)
            .onSuccess(new Continuation<AppLink, NavigationResult>() {
              @Override
              public NavigationResult then(Task<AppLink> task) throws Exception {
                return navigate(context, task.getResult());
              }
            }, Task.UI_THREAD_EXECUTOR);
  }

  /**
   * Navigates to an {@link bolts.AppLink} for the given destination using the App Link resolution
   * strategy specified.
   *
   * @param context     the Context from which the navigation should be performed.
   * @param destination the destination URL for the App Link.
   * @param resolver    the resolver to use for fetching App Link metadata.
   * @return the {@link bolts.AppLinkNavigation.NavigationResult} performed by navigating.
   */
  public static Task<NavigationResult> navigateInBackground(Context context,
                                                            URL destination,
                                                            AppLinkResolver resolver) {
    return navigateInBackground(context, Uri.parse(destination.toString()), resolver);
  }

  /**
   * Navigates to an {@link bolts.AppLink} for the given destination using the App Link resolution
   * strategy specified.
   *
   * @param context        the Context from which the navigation should be performed.
   * @param destinationUrl the destination URL for the App Link.
   * @param resolver       the resolver to use for fetching App Link metadata.
   * @return the {@link bolts.AppLinkNavigation.NavigationResult} performed by navigating.
   */
  public static Task<NavigationResult> navigateInBackground(Context context,
                                                            String destinationUrl,
                                                            AppLinkResolver resolver) {
    return navigateInBackground(context, Uri.parse(destinationUrl), resolver);
  }

  /**
   * Navigates to an {@link bolts.AppLink} for the given destination using the default
   * App Link resolution strategy.
   *
   * @param context     the Context from which the navigation should be performed.
   * @param destination the destination URL for the App Link.
   * @return the {@link bolts.AppLinkNavigation.NavigationResult} performed by navigating.
   */
  public static Task<NavigationResult> navigateInBackground(Context context,
                                                            Uri destination) {
    return navigateInBackground(context,
            destination,
            getResolver(context));
  }

  /**
   * Navigates to an {@link bolts.AppLink} for the given destination using the default
   * App Link resolution strategy.
   *
   * @param context     the Context from which the navigation should be performed.
   * @param destination the destination URL for the App Link.
   * @return the {@link bolts.AppLinkNavigation.NavigationResult} performed by navigating.
   */
  public static Task<NavigationResult> navigateInBackground(Context context,
                                                            URL destination) {
    return navigateInBackground(context,
            destination,
            getResolver(context));
  }

  /**
   * Navigates to an {@link bolts.AppLink} for the given destination using the default
   * App Link resolution strategy.
   *
   * @param context        the Context from which the navigation should be performed.
   * @param destinationUrl the destination URL for the App Link.
   * @return the {@link bolts.AppLinkNavigation.NavigationResult} performed by navigating.
   */
  public static Task<NavigationResult> navigateInBackground(Context context,
                                                            String destinationUrl) {
    return navigateInBackground(context,
            destinationUrl,
            getResolver(context));
  }
}




Java Source Code List

bolts.AggregateException.java
bolts.AndroidExecutorsTest.java
bolts.AndroidExecutors.java
bolts.AppLinkNavigation.java
bolts.AppLinkResolver.java
bolts.AppLinkTest.java
bolts.AppLink.java
bolts.AppLinks.java
bolts.BoltsExecutors.java
bolts.Bolts.java
bolts.Capture.java
bolts.Continuation.java
bolts.MeasurementEvent.java
bolts.TaskTest.java
bolts.Task.java
bolts.WebViewAppLinkResolver.java
bolts.utils.BoltsActivity2.java
bolts.utils.BoltsActivity.java