Blame | Last modification | View Log | RSS feed
/*** Copyright 2010-present Facebook.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.facebook;import android.app.Activity;import android.content.Context;import android.content.Intent;import android.net.Uri;import android.os.Bundle;import android.text.TextUtils;import android.util.Log;import com.facebook.internal.AttributionIdentifiers;import com.facebook.internal.Utility;import com.facebook.internal.Validate;import com.facebook.model.GraphObject;import org.json.JSONArray;import org.json.JSONException;import org.json.JSONObject;import java.util.Iterator;/*** Class to encapsulate an app link, and provide methods for constructing the data from various sources*/public class AppLinkData {/*** Key that should be used to pull out the UTC Unix tap-time from the arguments for this app link.*/public static final String ARGUMENTS_TAPTIME_KEY = "com.facebook.platform.APPLINK_TAP_TIME_UTC";/*** Key that should be used to get the "referer_data" field for this app link.*/public static final String ARGUMENTS_REFERER_DATA_KEY = "referer_data";/*** Key that should be used to pull out the native class that would have been used if the applink was deferred.*/public static final String ARGUMENTS_NATIVE_CLASS_KEY = "com.facebook.platform.APPLINK_NATIVE_CLASS";/*** Key that should be used to pull out the native url that would have been used if the applink was deferred.*/public static final String ARGUMENTS_NATIVE_URL = "com.facebook.platform.APPLINK_NATIVE_URL";static final String BUNDLE_APPLINK_ARGS_KEY = "com.facebook.platform.APPLINK_ARGS";private static final String BUNDLE_AL_APPLINK_DATA_KEY = "al_applink_data";private static final String APPLINK_BRIDGE_ARGS_KEY = "bridge_args";private static final String APPLINK_METHOD_ARGS_KEY = "method_args";private static final String APPLINK_VERSION_KEY = "version";private static final String BRIDGE_ARGS_METHOD_KEY = "method";private static final String DEFERRED_APP_LINK_EVENT = "DEFERRED_APP_LINK";private static final String DEFERRED_APP_LINK_PATH = "%s/activities";private static final String DEFERRED_APP_LINK_ARGS_FIELD = "applink_args";private static final String DEFERRED_APP_LINK_CLASS_FIELD = "applink_class";private static final String DEFERRED_APP_LINK_CLICK_TIME_FIELD = "click_time";private static final String DEFERRED_APP_LINK_URL_FIELD = "applink_url";private static final String METHOD_ARGS_TARGET_URL_KEY = "target_url";private static final String METHOD_ARGS_REF_KEY = "ref";private static final String REFERER_DATA_REF_KEY = "fb_ref";private static final String TAG = AppLinkData.class.getCanonicalName();private String ref;private Uri targetUri;private JSONObject arguments;private Bundle argumentBundle;/*** Asynchronously fetches app link information that might have been stored for use* after installation of the app* @param context The context* @param completionHandler CompletionHandler to be notified with the AppLinkData object or null if none is* available. Must not be null.*/public static void fetchDeferredAppLinkData(Context context, CompletionHandler completionHandler) {fetchDeferredAppLinkData(context, null, completionHandler);}/*** Asynchronously fetches app link information that might have been stored for use* after installation of the app* @param context The context* @param applicationId Facebook application Id. If null, it is taken from the manifest* @param completionHandler CompletionHandler to be notified with the AppLinkData object or null if none is* available. Must not be null.*/public static void fetchDeferredAppLinkData(Context context,String applicationId,final CompletionHandler completionHandler) {Validate.notNull(context, "context");Validate.notNull(completionHandler, "completionHandler");if (applicationId == null) {applicationId = Utility.getMetadataApplicationId(context);}Validate.notNull(applicationId, "applicationId");final Context applicationContext = context.getApplicationContext();final String applicationIdCopy = applicationId;Settings.getExecutor().execute(new Runnable() {@Overridepublic void run() {fetchDeferredAppLinkFromServer(applicationContext, applicationIdCopy, completionHandler);}});}private static void fetchDeferredAppLinkFromServer(Context context,String applicationId,final CompletionHandler completionHandler) {GraphObject deferredApplinkParams = GraphObject.Factory.create();deferredApplinkParams.setProperty("event", DEFERRED_APP_LINK_EVENT);Utility.setAppEventAttributionParameters(deferredApplinkParams,AttributionIdentifiers.getAttributionIdentifiers(context),Utility.getHashedDeviceAndAppID(context, applicationId),Settings.getLimitEventAndDataUsage(context));deferredApplinkParams.setProperty("application_package_name", context.getPackageName());String deferredApplinkUrlPath = String.format(DEFERRED_APP_LINK_PATH, applicationId);AppLinkData appLinkData = null;try {Request deferredApplinkRequest = Request.newPostRequest(null, deferredApplinkUrlPath, deferredApplinkParams, null);Response deferredApplinkResponse = deferredApplinkRequest.executeAndWait();GraphObject wireResponse = deferredApplinkResponse.getGraphObject();JSONObject jsonResponse = wireResponse != null ? wireResponse.getInnerJSONObject() : null;if (jsonResponse != null) {final String appLinkArgsJsonString = jsonResponse.optString(DEFERRED_APP_LINK_ARGS_FIELD);final long tapTimeUtc = jsonResponse.optLong(DEFERRED_APP_LINK_CLICK_TIME_FIELD, -1);final String appLinkClassName = jsonResponse.optString(DEFERRED_APP_LINK_CLASS_FIELD);final String appLinkUrl = jsonResponse.optString(DEFERRED_APP_LINK_URL_FIELD);if (!TextUtils.isEmpty(appLinkArgsJsonString)) {appLinkData = createFromJson(appLinkArgsJsonString);if (tapTimeUtc != -1) {try {if (appLinkData.arguments != null) {appLinkData.arguments.put(ARGUMENTS_TAPTIME_KEY, tapTimeUtc);}if (appLinkData.argumentBundle != null) {appLinkData.argumentBundle.putString(ARGUMENTS_TAPTIME_KEY, Long.toString(tapTimeUtc));}} catch (JSONException e) {Log.d(TAG, "Unable to put tap time in AppLinkData.arguments");}}if (appLinkClassName != null) {try {if (appLinkData.arguments != null) {appLinkData.arguments.put(ARGUMENTS_NATIVE_CLASS_KEY, appLinkClassName);}if (appLinkData.argumentBundle != null) {appLinkData.argumentBundle.putString(ARGUMENTS_NATIVE_CLASS_KEY, appLinkClassName);}} catch (JSONException e) {Log.d(TAG, "Unable to put tap time in AppLinkData.arguments");}}if (appLinkUrl != null) {try {if (appLinkData.arguments != null) {appLinkData.arguments.put(ARGUMENTS_NATIVE_URL, appLinkUrl);}if (appLinkData.argumentBundle != null) {appLinkData.argumentBundle.putString(ARGUMENTS_NATIVE_URL, appLinkUrl);}} catch (JSONException e) {Log.d(TAG, "Unable to put tap time in AppLinkData.arguments");}}}}} catch (Exception e) {Utility.logd(TAG, "Unable to fetch deferred applink from server");}completionHandler.onDeferredAppLinkDataFetched(appLinkData);}/*** Parses out any app link data from the Intent of the Activity passed in.* @param activity Activity that was started because of an app link* @return AppLinkData if found. null if not.*/public static AppLinkData createFromActivity(Activity activity) {Validate.notNull(activity, "activity");Intent intent = activity.getIntent();if (intent == null) {return null;}AppLinkData appLinkData = createFromAlApplinkData(intent);if (appLinkData == null) {String appLinkArgsJsonString = intent.getStringExtra(BUNDLE_APPLINK_ARGS_KEY);appLinkData = createFromJson(appLinkArgsJsonString);}if (appLinkData == null) {// Try regular app linkingappLinkData = createFromUri(intent.getData());}return appLinkData;}private static AppLinkData createFromAlApplinkData(Intent intent) {Bundle applinks = intent.getBundleExtra(BUNDLE_AL_APPLINK_DATA_KEY);if (applinks == null) {return null;}AppLinkData appLinkData = new AppLinkData();appLinkData.targetUri = intent.getData();if (appLinkData.targetUri == null) {String targetUriString = applinks.getString(METHOD_ARGS_TARGET_URL_KEY);if (targetUriString != null) {appLinkData.targetUri = Uri.parse(targetUriString);}}appLinkData.argumentBundle = applinks;appLinkData.arguments = null;Bundle refererData = applinks.getBundle(ARGUMENTS_REFERER_DATA_KEY);if (refererData != null) {appLinkData.ref = refererData.getString(REFERER_DATA_REF_KEY);}return appLinkData;}private static AppLinkData createFromJson(String jsonString) {if (jsonString == null) {return null;}try {// Any missing or malformed data will result in a JSONExceptionJSONObject appLinkArgsJson = new JSONObject(jsonString);String version = appLinkArgsJson.getString(APPLINK_VERSION_KEY);JSONObject bridgeArgs = appLinkArgsJson.getJSONObject(APPLINK_BRIDGE_ARGS_KEY);String method = bridgeArgs.getString(BRIDGE_ARGS_METHOD_KEY);if (method.equals("applink") && version.equals("2")) {// We have a new deep linkAppLinkData appLinkData = new AppLinkData();appLinkData.arguments = appLinkArgsJson.getJSONObject(APPLINK_METHOD_ARGS_KEY);// first look for the "ref" key in the top level argsif (appLinkData.arguments.has(METHOD_ARGS_REF_KEY)) {appLinkData.ref = appLinkData.arguments.getString(METHOD_ARGS_REF_KEY);} else if (appLinkData.arguments.has(ARGUMENTS_REFERER_DATA_KEY)) {// if it's not in the top level args, it could be in the "referer_data" blobJSONObject refererData = appLinkData.arguments.getJSONObject(ARGUMENTS_REFERER_DATA_KEY);if (refererData.has(REFERER_DATA_REF_KEY)) {appLinkData.ref = refererData.getString(REFERER_DATA_REF_KEY);}}if (appLinkData.arguments.has(METHOD_ARGS_TARGET_URL_KEY)) {appLinkData.targetUri = Uri.parse(appLinkData.arguments.getString(METHOD_ARGS_TARGET_URL_KEY));}appLinkData.argumentBundle = toBundle(appLinkData.arguments);return appLinkData;}} catch (JSONException e) {Log.d(TAG, "Unable to parse AppLink JSON", e);} catch (FacebookException e) {Log.d(TAG, "Unable to parse AppLink JSON", e);}return null;}private static AppLinkData createFromUri(Uri appLinkDataUri) {if (appLinkDataUri == null) {return null;}AppLinkData appLinkData = new AppLinkData();appLinkData.targetUri = appLinkDataUri;return appLinkData;}private static Bundle toBundle(JSONObject node) throws JSONException {Bundle bundle = new Bundle();@SuppressWarnings("unchecked")Iterator<String> fields = node.keys();while (fields.hasNext()) {String key = fields.next();Object value;value = node.get(key);if (value instanceof JSONObject) {bundle.putBundle(key, toBundle((JSONObject) value));} else if (value instanceof JSONArray) {JSONArray valueArr = (JSONArray) value;if (valueArr.length() == 0) {bundle.putStringArray(key, new String[0]);} else {Object firstNode = valueArr.get(0);if (firstNode instanceof JSONObject) {Bundle[] bundles = new Bundle[valueArr.length()];for (int i = 0; i < valueArr.length(); i++) {bundles[i] = toBundle(valueArr.getJSONObject(i));}bundle.putParcelableArray(key, bundles);} else if (firstNode instanceof JSONArray) {// we don't support nested arraysthrow new FacebookException("Nested arrays are not supported.");} else { // just use the string valueString[] arrValues = new String[valueArr.length()];for (int i = 0; i < valueArr.length(); i++) {arrValues[i] = valueArr.get(i).toString();}bundle.putStringArray(key, arrValues);}}} else {bundle.putString(key, value.toString());}}return bundle;}private AppLinkData() {}/*** Returns the target uri for this App Link.* @return target uri*/public Uri getTargetUri() {return targetUri;}/*** Returns the ref for this App Link.* @return ref*/public String getRef() {return ref;}/*** This method has been deprecated. Please use {@link AppLinkData#getArgumentBundle()} instead.* @return JSONObject property bag.*/@Deprecatedpublic JSONObject getArguments() {return arguments;}/*** The full set of arguments for this app link. Properties like target uri & ref are typically* picked out of this set of arguments.* @return App link related arguments as a bundle.*/public Bundle getArgumentBundle() {return argumentBundle;}/*** The referer data associated with the app link. This will contain Facebook specific information like* fb_access_token, fb_expires_in, and fb_ref.* @return the referer data.*/public Bundle getRefererData() {if (argumentBundle != null) {return argumentBundle.getBundle(ARGUMENTS_REFERER_DATA_KEY);}return null;}/*** Interface to asynchronously receive AppLinkData after it has been fetched.*/public interface CompletionHandler {/*** This method is called when deferred app link data has been fetched. If no app link data was found,* this method is called with null* @param appLinkData The app link data that was fetched. Null if none was found.*/void onDeferredAppLinkDataFetched(AppLinkData appLinkData);}}