chromium/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FreIntentCreator.java

// Copyright 2019 The Chromium Authors
// 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.firstrun;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
import android.text.TextUtils;

import androidx.annotation.Nullable;

import org.chromium.base.ApplicationStatus;
import org.chromium.base.IntentUtils;
import org.chromium.chrome.browser.AppHooks;
import org.chromium.chrome.browser.LaunchIntentDispatcher;
import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.browserservices.intents.WebApkExtras;
import org.chromium.chrome.browser.browserservices.intents.WebappExtras;
import org.chromium.chrome.browser.webapps.WebappLauncherActivity;

/**
 * This class makes a decision what FRE type to launch and creates a corresponding intent. Should be
 * instantiated using {@link AppHooks#createFreIntentCreator}.
 */
public class FreIntentCreator {
    /**
     * Creates an intent to launch the First Run Experience.
     *
     * @param caller               Activity instance that is requesting the first run.
     * @param fromIntent           Intent used to launch the caller.
     * @param preferLightweightFre Whether to prefer the Lightweight First Run Experience.
     * @return Intent to launch First Run Experience.
     */
    public Intent create(Context caller, Intent fromIntent, boolean preferLightweightFre) {
        @Nullable
        BrowserServicesIntentDataProvider webApkIntentDataProvider =
                WebappLauncherActivity.maybeSlowlyGenerateWebApkIntentDataProviderFromIntent(
                        fromIntent);

        String associatedAppName = null;
        Intent intentToLaunchAfterFreComplete = fromIntent;
        if (webApkIntentDataProvider != null
                && webApkIntentDataProvider.getWebApkExtras() != null) {
            WebappExtras webappExtras = webApkIntentDataProvider.getWebappExtras();
            associatedAppName = webappExtras.shortName;

            WebApkExtras webApkExtras = webApkIntentDataProvider.getWebApkExtras();
            intentToLaunchAfterFreComplete =
                    WebappLauncherActivity.createRelaunchWebApkIntent(
                            fromIntent, webApkExtras.webApkPackageName, webappExtras.url);
        }

        Intent result = createInternal(caller, fromIntent, preferLightweightFre, associatedAppName);
        addPendingIntent(caller, result, intentToLaunchAfterFreComplete);
        return result;
    }

    /**
     * Selects one specific FRE implementation and creates an intent to launch this implementation.
     * Called by {@link #create} that also adds some final touches to the returned intent.
     *
     * @param caller               Activity instance that is requesting the first run.
     * @param fromIntent           Intent used to launch the caller.
     * @param preferLightweightFre Whether to prefer the Lightweight First Run Experience.
     * @param associatedAppName    WebAPK short name if this FRE flow was triggered by launching a
     *                             WebAPK. Null otherwise.
     * @return Intent to launch First Run Experience.
     */
    protected Intent createInternal(
            Context caller,
            Intent fromIntent,
            boolean preferLightweightFre,
            @Nullable String associatedAppName) {
        // Launch the Generic First Run Experience if it was previously active.
        boolean isGenericFreActive = checkIsGenericFreActive();
        if (preferLightweightFre && !isGenericFreActive) {
            return createLightweightFirstRunIntent(caller, associatedAppName);
        } else {
            return createGenericFirstRunIntent(caller, fromIntent);
        }
    }

    /**
     * Returns an intent to show the lightweight first run activity.
     * @param context                        The context.
     * @param associatedAppName              WebAPK short name if this FRE flow was triggered by
     *                                       launching a WebAPK. Null otherwise.
     */
    private static Intent createLightweightFirstRunIntent(
            Context context, @Nullable String associatedAppName) {
        Intent intent = new Intent(context, LightweightFirstRunActivity.class);
        if (associatedAppName != null) {
            intent.putExtra(
                    LightweightFirstRunActivity.EXTRA_ASSOCIATED_APP_NAME, associatedAppName);
        }
        return intent;
    }

    /**
     * Returns a generic intent to show the First Run Activity.
     *
     * @param context The context.
     * @param fromIntent The intent that was used to launch Chrome.
     */
    private static Intent createGenericFirstRunIntent(Context context, Intent fromIntent) {
        Intent intent = new Intent(context, FirstRunActivity.class);
        intent.putExtra(
                FirstRunActivity.EXTRA_COMING_FROM_CHROME_ICON,
                TextUtils.equals(fromIntent.getAction(), Intent.ACTION_MAIN));
        intent.putExtra(
                FirstRunActivity.EXTRA_CHROME_LAUNCH_INTENT_IS_CCT,
                LaunchIntentDispatcher.isCustomTabIntent(fromIntent));
        intent.putExtra(
                FirstRunActivityBase.EXTRA_FRE_INTENT_CREATION_ELAPSED_REALTIME_MS,
                SystemClock.elapsedRealtime());

        // Copy extras bundle from intent which was used to launch Chrome. Copying the extras
        // enables the FirstRunActivity to locate the associated CustomTabsSession (if there
        // is one) and to notify the connection of whether the FirstRunActivity was completed.
        Bundle fromIntentExtras = fromIntent.getExtras();
        if (fromIntentExtras != null) {
            Bundle copiedFromExtras = new Bundle(fromIntentExtras);
            intent.putExtra(FirstRunActivity.EXTRA_CHROME_LAUNCH_INTENT_EXTRAS, copiedFromExtras);
        }

        return intent;
    }

    /**
     * Adds fromIntent as a PendingIntent to the firstRunIntent. This should be used to add a
     * PendingIntent that will be sent when first run is completed.
     *
     * @param context                        The context that corresponds to the Intent.
     * @param firstRunIntent                 The intent that will be used to start first run.
     * @param intentToLaunchAfterFreComplete The intent to launch when the user completes the FRE.
     */
    private static void addPendingIntent(
            Context context, Intent firstRunIntent, Intent intentToLaunchAfterFreComplete) {
        int pendingIntentFlags =
                PendingIntent.FLAG_UPDATE_CURRENT
                        | PendingIntent.FLAG_ONE_SHOT
                        | IntentUtils.getPendingIntentMutabilityFlag(false);
        PendingIntent pendingIntent =
                PendingIntent.getActivity(
                        context, 0, intentToLaunchAfterFreComplete, pendingIntentFlags);
        firstRunIntent.putExtra(FirstRunActivity.EXTRA_FRE_COMPLETE_LAUNCH_INTENT, pendingIntent);
    }

    /** Returns whether the generic FRE is active. */
    private static boolean checkIsGenericFreActive() {
        for (Activity activity : ApplicationStatus.getRunningActivities()) {
            // LightweightFirstRunActivity does not extends FirstRunActivity.
            if (activity instanceof FirstRunActivity) {
                return true;
            }
        }
        return false;
    }
}