chromium/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/IncognitoStartup.java

// Copyright 2021 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.incognito;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager.AppTask;
import android.app.ActivityManager.RecentTaskInfo;
import android.util.Pair;

import org.chromium.base.ApplicationStatus;
import org.chromium.base.ContextUtils;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.chrome.browser.cookies.CookiesFetcher;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.tabmodel.IncognitoTabHostUtils;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.util.AndroidTaskUtils;

import java.util.HashSet;
import java.util.Set;

/** Operations that need to be executed on startup for incognito mode. */
public class IncognitoStartup {
    public static void onResumeWithNative(
            ObservableSupplier<TabModelSelector> tabModelSelectorSupplier,
            Set<String> componentNames) {
        if (shouldDestroyIncognitoProfileOnStartup(
                tabModelSelectorSupplier.get().getCurrentModel().isIncognito(), componentNames)) {
            ProfileManager.destroyWhenAppropriate(
                    ProfileManager.getLastUsedRegularProfile()
                            .getPrimaryOTRProfile(/* createIfNeeded= */ true));
        } else {
            CookiesFetcher.restoreCookies();
        }
    }

    /**
     * Determine whether the incognito profile needs to be destroyed as part of startup.  This is
     * only needed on L+ when it is possible to swipe away tasks from Android recents without
     * killing the process.  When this occurs, the normal incognito profile shutdown does not
     * happen, which can leave behind incognito cookies from an existing session.
     */
    @SuppressLint("NewApi")
    private static boolean shouldDestroyIncognitoProfileOnStartup(
            boolean selectedTabModelIsIncognito, Set<String> componentNames) {
        if (!ProfileManager.getLastUsedRegularProfile().hasPrimaryOTRProfile()) {
            return false;
        }

        Set<Pair<AppTask, RecentTaskInfo>> tabbedModeTasks =
                AndroidTaskUtils.getRecentAppTasksMatchingComponentNames(
                        ContextUtils.getApplicationContext(), componentNames);

        Set<Integer> tabbedModeTaskIds = new HashSet<>();
        for (Pair<AppTask, RecentTaskInfo> pair : tabbedModeTasks) {
            tabbedModeTaskIds.add(pair.second.id);
        }

        if (tabbedModeTaskIds.size() == 0) {
            return true;
        }

        for (Activity activity : ApplicationStatus.getRunningActivities()) {
            tabbedModeTaskIds.remove(activity.getTaskId());
        }

        // If all tabbed mode tasks listed in Android recents are alive, check to see if
        // any incognito tabs exist and the current tab model isn't incognito. If so, we should
        // destroy the incognito profile; otherwise it's not safe to do so yet.
        if (tabbedModeTaskIds.size() == 0) {
            return !(IncognitoTabHostUtils.doIncognitoTabsExist() || selectedTabModelIsIncognito);
        }

        // In this case, we have tabbed mode activities listed in recents that do not have an
        // active running activity associated with them.  We can not accurately get an incognito
        // tab count as we do not know if any incognito tabs are associated with the yet unrestored
        // tabbed mode.  Thus we do not proactively destroy the incognito profile.
        return false;
    }
}