All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bolts.AppLinkNavigation Maven / Gradle / Ivy

Go to download

Bolts is a collection of low-level libraries designed to make developing mobile apps easier.

There is a newer version: 1.4.0
Show newest version
/*
 *  Copyright (c) 2014, Facebook, Inc.
 *  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 extraLoggingData = new HashMap();
    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 navigateInBackground(final Context context,
                                                            Uri destination,
                                                            AppLinkResolver resolver) {
    return resolver.getAppLinkFromUrlInBackground(destination)
            .onSuccess(new Continuation() {
              @Override
              public NavigationResult then(Task 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 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 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 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 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 navigateInBackground(Context context,
                                                            String destinationUrl) {
    return navigateInBackground(context,
            destinationUrl,
            getResolver(context));
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy