// Copyright 2014 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.tabmodel.document; import android.annotation.TargetApi; import android.app.Activity; import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Build; import android.text.TextUtils; import org.chromium.base.ApplicationStatus; import org.chromium.base.ContextUtils; import org.chromium.chrome.browser.ChromeActivity; import org.chromium.chrome.browser.IntentHandler; import org.chromium.chrome.browser.UrlConstants; import org.chromium.chrome.browser.document.DocumentActivity; import org.chromium.chrome.browser.document.DocumentUtils; import org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tabmodel.AsyncTabParamsManager; import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel.Entry; import org.chromium.chrome.browser.util.IntentUtils; import java.lang.ref.WeakReference; import java.util.List; /** * Interfaces with the ActivityManager to identify Tabs/Tasks that are being tracked by * Android's Recents list. */ public abstract class ActivityDelegate { private final Class<?> mRegularClass; private final Class<?> mIncognitoClass; /** * Creates a ActivityDelegate. * @param regularClass Class of the regular DocumentActivity. * @param incognitoClass Class of the Incognito DocumentActivity. */ public ActivityDelegate(Class<?> regularClass, Class<?> incognitoClass) { mRegularClass = regularClass; mIncognitoClass = incognitoClass; } /** * Returns whether an Activity is a DocumentActivity. Assumes the Incognito Activity inherits * from the regular Activity. * @param activity Activity to check. * @return Whether the Activity is a DocumentActivity. */ public boolean isDocumentActivity(Activity activity) { return mRegularClass.isInstance(activity); } /** * Checks whether or not the Intent corresponds to an Activity that should be tracked. * @param isIncognito Whether or not the TabModel is managing incognito tabs. * @param intent Intent used to launch the Activity. * @return Whether or not to track the Activity. */ public boolean isValidActivity(boolean isIncognito, Intent intent) { if (intent == null) return false; String desiredClassName = isIncognito ? mIncognitoClass.getName() : mRegularClass.getName(); String desiredLegacyClassName = isIncognito ? DocumentActivity.LEGACY_INCOGNITO_CLASS_NAME : DocumentActivity.LEGACY_CLASS_NAME; String className = null; if (intent.getComponent() == null) { ResolveInfo resolveInfo = ExternalNavigationDelegateImpl.resolveActivity(intent); if (resolveInfo != null) className = resolveInfo.activityInfo.name; } else { className = intent.getComponent().getClassName(); } return TextUtils.equals(className, desiredClassName) || TextUtils.equals(className, desiredLegacyClassName); } /** * Finishes all DocumentActivities that appear in Android's Recents. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void finishAllDocumentActivities() { Context context = ContextUtils.getApplicationContext(); ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.AppTask task : activityManager.getAppTasks()) { Intent intent = DocumentUtils.getBaseIntentFromTask(task); if (isValidActivity(false, intent) || isValidActivity(true, intent)) { task.finishAndRemoveTask(); } } } /** * Get a map of the Chrome tasks displayed by Android's Recents. * @param isIncognito Whether or not the TabList is managing incognito tabs. */ public abstract List<Entry> getTasksFromRecents(boolean isIncognito); /** * Moves the given task to the front, if it exists. * @param isIncognito Whether or not the TabList is managing incognito tabs. * @param tabId ID of the tab to move to front. */ public abstract void moveTaskToFront(boolean isIncognito, int tabId); /** * Finishes and removes the task. * @param isIncognito Whether or not the TabList is managing incognito tabs. * @param tabId ID of the tab to move to front. */ public abstract void finishAndRemoveTask(boolean isIncognito, int tabId); /** * Check if the Tab is associated with an Activity that hasn't been destroyed. * This catches situations where a DocumentActivity is no longer listed in Android's Recents * list, but is not dead yet. * @param tabId ID of the Tab to find. * @return Whether the tab is still alive. */ public boolean isTabAssociatedWithNonDestroyedActivity(boolean isIncognito, int tabId) { List<WeakReference<Activity>> activities = ApplicationStatus.getRunningActivities(); for (WeakReference<Activity> ref : activities) { Activity activity = ref.get(); if (activity != null && isValidActivity(isIncognito, activity.getIntent()) && getTabIdFromIntent(activity.getIntent()) == tabId && !isActivityDestroyed(activity)) { return true; } } return false; } /** * Check whether or not the Intent contains an ID for document mode. * @param intent Intent to check. * @return ID for the document that has the given intent as base intent, or * {@link Tab.INVALID_TAB_ID} if it couldn't be retrieved. */ public static int getTabIdFromIntent(Intent intent) { if (intent == null || intent.getData() == null) return Tab.INVALID_TAB_ID; // Avoid AsyncTabCreationParams related flows early returning here. if (AsyncTabParamsManager.hasParamsWithTabToReparent()) { return IntentUtils.safeGetIntExtra( intent, IntentHandler.EXTRA_TAB_ID, Tab.INVALID_TAB_ID); } Uri data = intent.getData(); if (!TextUtils.equals(data.getScheme(), UrlConstants.DOCUMENT_SCHEME)) { return Tab.INVALID_TAB_ID; } try { return Integer.parseInt(data.getHost()); } catch (NumberFormatException e) { return Tab.INVALID_TAB_ID; } } /** * Parse out the URL for a document Intent. * @param intent Intent to check. * @return The URL that the Intent was fired to display, or null if it couldn't be retrieved. */ public static String getInitialUrlForDocument(Intent intent) { if (intent == null || intent.getData() == null) return null; Uri data = intent.getData(); return TextUtils.equals(data.getScheme(), UrlConstants.DOCUMENT_SCHEME) ? data.getQuery() : null; } /** * @return Whether any incognito tabs are visible to the user in Android's Overview list. */ public abstract boolean isIncognitoDocumentAccessibleToUser(); /** * @return Running Activity that owns the given Tab, null if the Activity couldn't be found. */ public static Activity getActivityForTabId(int id) { if (id == Tab.INVALID_TAB_ID) return null; for (WeakReference<Activity> ref : ApplicationStatus.getRunningActivities()) { if (!(ref.get() instanceof ChromeActivity)) continue; ChromeActivity activity = (ChromeActivity) ref.get(); if (activity == null) continue; if (activity.getTabModelSelector().getTabById(id) != null) return activity; } return null; } /** * @return Whether or not the given Activity is destroyed. */ protected abstract boolean isActivityDestroyed(Activity activity); }