| Index: chrome/android/java/src/org/chromium/chrome/browser/InstantAppsHandler.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/InstantAppsHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/InstantAppsHandler.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..3515dc84b7f0f7743c628bdbea66fdeba6a102f9
|
| --- /dev/null
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/InstantAppsHandler.java
|
| @@ -0,0 +1,225 @@
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.chrome.browser;
|
| +
|
| +import android.content.Context;
|
| +import android.content.Intent;
|
| +import android.os.StrictMode;
|
| +import android.os.SystemClock;
|
| +import android.provider.Browser;
|
| +
|
| +import org.chromium.base.CommandLine;
|
| +import org.chromium.base.FieldTrialList;
|
| +import org.chromium.base.Log;
|
| +import org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl;
|
| +import org.chromium.chrome.browser.metrics.LaunchMetrics.TimesHistogramSample;
|
| +import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
|
| +import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
|
| +import org.chromium.chrome.browser.util.IntentUtils;
|
| +
|
| +import java.util.concurrent.TimeUnit;
|
| +
|
| +/** A launcher for Instant Apps. */
|
| +public class InstantAppsHandler {
|
| + private static final String TAG = "InstantAppsHandler";
|
| +
|
| + private static final Object INSTANCE_LOCK = new Object();
|
| + private static InstantAppsHandler sInstance;
|
| +
|
| + private static final String DO_NOT_LAUNCH_EXTRA =
|
| + "com.google.android.gms.instantapps.DO_NOT_LAUNCH_INSTANT_APP";
|
| +
|
| + private static final String CUSTOM_APPS_INSTANT_APP_EXTRA =
|
| + "android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS";
|
| +
|
| + private static final String INSTANT_APP_START_TIME_EXTRA =
|
| + "org.chromium.chrome.INSTANT_APP_START_TIME";
|
| +
|
| + /** Finch experiment name. */
|
| + private static final String INSTANT_APPS_EXPERIMENT_NAME = "InstantApps";
|
| +
|
| + /** Finch experiment group which is enabled for instant apps. */
|
| + private static final String INSTANT_APPS_ENABLED_ARM = "InstantAppsEnabled";
|
| +
|
| + /** Finch experiment group which is disabled for instant apps. */
|
| + private static final String INSTANT_APPS_DISABLED_ARM = "InstantAppsDisabled";
|
| +
|
| + /** A histogram to record how long each handleIntent() call took. */
|
| + private static final TimesHistogramSample sHandleIntentDuration = new TimesHistogramSample(
|
| + "Android.InstantApps.HandleIntentDuration", TimeUnit.MILLISECONDS);
|
| +
|
| + /** A histogram to record how long the fallback intent roundtrip was. */
|
| + private static final TimesHistogramSample sFallbackIntentTimes = new TimesHistogramSample(
|
| + "Android.InstantApps.FallbackDuration", TimeUnit.MILLISECONDS);
|
| +
|
| + /** A histogram to record how long the GMS Core API call took. */
|
| + private static final TimesHistogramSample sInstantAppsApiCallTimes = new TimesHistogramSample(
|
| + "Android.InstantApps.ApiCallDuration", TimeUnit.MILLISECONDS);
|
| +
|
| +
|
| + /** @return The singleton instance of {@link InstantAppsHandler}. */
|
| + public static InstantAppsHandler getInstance(ChromeApplication application) {
|
| + synchronized (INSTANCE_LOCK) {
|
| + if (sInstance == null) {
|
| + sInstance = application.createInstantAppsHandler();
|
| + }
|
| + }
|
| + return sInstance;
|
| + }
|
| +
|
| + /**
|
| + * Check the cached value to figure out if the feature is enabled. We have to use the cached
|
| + * value because native library hasn't yet been loaded.
|
| + * @param context The application context.
|
| + * @return Whether the feature is enabled.
|
| + */
|
| + private boolean isEnabled(Context context) {
|
| + // Will go away once the feature is enabled for everyone by default.
|
| + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
|
| + try {
|
| + return ChromePreferenceManager.getInstance(context).getCachedInstantAppsEnabled();
|
| + } finally {
|
| + StrictMode.setThreadPolicy(oldPolicy);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Record how long the handleIntent() method took.
|
| + * @param startTime The timestamp for handleIntent start time.
|
| + */
|
| + private void recordHandleIntentDuration(long startTime) {
|
| + sHandleIntentDuration.record(SystemClock.elapsedRealtime() - startTime);
|
| + }
|
| +
|
| + /**
|
| + * Record the amount of time spent in the instant apps API call.
|
| + * @param startTime The time at which we started doing computations.
|
| + */
|
| + protected void recordInstantAppsApiCallTime(long startTime) {
|
| + sInstantAppsApiCallTimes.record(SystemClock.elapsedRealtime() - startTime);
|
| + }
|
| +
|
| + /**
|
| + * In the case Chrome is called through the fallback mechanism from Instant Apps, record the
|
| + * amount of time the whole trip took.
|
| + * @param intent The current intent.
|
| + */
|
| + private void maybeRecordFallbackDuration(Intent intent) {
|
| + if (intent.hasExtra(INSTANT_APP_START_TIME_EXTRA)) {
|
| + Long startTime = intent.getLongExtra(INSTANT_APP_START_TIME_EXTRA, 0);
|
| + if (startTime != 0) {
|
| + sFallbackIntentTimes.record(SystemClock.elapsedRealtime() - startTime);
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Cache whether the Instant Apps feature is enabled.
|
| + * This should only be called with the native library loaded.
|
| + */
|
| + public void cacheInstantAppsEnabled(Context context) {
|
| + boolean isEnabled = false;
|
| + boolean wasEnabled = isEnabled(context);
|
| + CommandLine instance = CommandLine.getInstance();
|
| + if (instance.hasSwitch(ChromeSwitches.DISABLE_APP_LINK)) {
|
| + isEnabled = false;
|
| + } else if (instance.hasSwitch(ChromeSwitches.ENABLE_APP_LINK)) {
|
| + isEnabled = true;
|
| + } else {
|
| + String experiment = FieldTrialList.findFullName(INSTANT_APPS_EXPERIMENT_NAME);
|
| + if (INSTANT_APPS_DISABLED_ARM.equals(experiment)) {
|
| + isEnabled = false;
|
| + } else if (INSTANT_APPS_ENABLED_ARM.equals(experiment)) {
|
| + isEnabled = true;
|
| + }
|
| + }
|
| +
|
| + if (isEnabled != wasEnabled) {
|
| + ChromePreferenceManager.getInstance(context).setCachedInstantAppsEnabled(isEnabled);
|
| + }
|
| + }
|
| +
|
| + /** Handle incoming intent. */
|
| + public boolean handleIncomingIntent(Context context, Intent intent,
|
| + boolean isCustomTabsIntent) {
|
| + long startTimeStamp = SystemClock.elapsedRealtime();
|
| + boolean result = handleIncomingIntentInternal(context, intent, isCustomTabsIntent,
|
| + startTimeStamp);
|
| + recordHandleIntentDuration(startTimeStamp);
|
| + return result;
|
| + }
|
| +
|
| + private boolean handleIncomingIntentInternal(
|
| + Context context, Intent intent, boolean isCustomTabsIntent, long startTime) {
|
| + if (!isEnabled(context)
|
| + || IntentUtils.safeGetBooleanExtra(intent, DO_NOT_LAUNCH_EXTRA, false)
|
| + || IntentUtils.safeGetBooleanExtra(
|
| + intent, IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, false)
|
| + || (isCustomTabsIntent && !IntentUtils.safeGetBooleanExtra(
|
| + intent, CUSTOM_APPS_INSTANT_APP_EXTRA, false))
|
| + || DataReductionProxySettings.isEnabledBeforeNativeLoad(context)
|
| + || isIntentFromChrome(context, intent)
|
| + || (intent.getAction() != Intent.ACTION_VIEW)) {
|
| + Log.i(TAG, "Not handling with Instant Apps");
|
| + return false;
|
| + }
|
| +
|
| + maybeRecordFallbackDuration(intent);
|
| +
|
| + // Used to search for the intent handlers. Needs null component to return correct results.
|
| + Intent intentCopy = new Intent(intent);
|
| + intentCopy.setComponent(null);
|
| +
|
| + if (!(isCustomTabsIntent || isChromeDefaultHandler(context))
|
| + || ExternalNavigationDelegateImpl.isPackageSpecializedHandler(
|
| + context, null, intentCopy)) {
|
| + // Chrome is not the default browser or a specialized handler exists.
|
| + Log.i(TAG, "Not handling with Instant Apps because Chrome is not default or "
|
| + + "there's a specialized handler");
|
| + return false;
|
| + }
|
| +
|
| + Intent callbackIntent = new Intent(intent);
|
| + callbackIntent.putExtra(DO_NOT_LAUNCH_EXTRA, true);
|
| + callbackIntent.putExtra(INSTANT_APP_START_TIME_EXTRA, startTime);
|
| +
|
| + return tryLaunchingInstantApp(context, intent, isCustomTabsIntent, callbackIntent);
|
| + }
|
| +
|
| + /**
|
| + * Attempts to launch an Instant App, if possible.
|
| + * @param context The activity context.
|
| + * @param intent The incoming intent.
|
| + * @param isCustomTabsIntent Whether the intent is for a CustomTab.
|
| + * @param fallbackIntent The intent that will be launched by Instant Apps in case of failure to
|
| + * load.
|
| + * @return Whether an Instant App was launched.
|
| + */
|
| + protected boolean tryLaunchingInstantApp(
|
| + Context context, Intent intent, boolean isCustomTabsIntent, Intent fallbackIntent) {
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * @return Whether the intent was fired from Chrome. This happens when the user gets a
|
| + * disambiguation dialog and chooses to stay in Chrome.
|
| + */
|
| + private boolean isIntentFromChrome(Context context, Intent intent) {
|
| + return context.getPackageName().equals(IntentUtils.safeGetStringExtra(
|
| + intent, Browser.EXTRA_APPLICATION_ID))
|
| + // We shouldn't leak internal intents with authentication tokens
|
| + || IntentHandler.wasIntentSenderChrome(intent, context);
|
| + }
|
| +
|
| + /** @return Whether Chrome is the default browser on the device. */
|
| + private boolean isChromeDefaultHandler(Context context) {
|
| + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
|
| + try {
|
| + return ChromePreferenceManager.getInstance(context).getCachedChromeDefaultBrowser();
|
| + } finally {
|
| + StrictMode.setThreadPolicy(oldPolicy);
|
| + }
|
| + }
|
| +}
|
|
|