@TargetApi(Build.VERSION_CODES.LOLLIPOP) private void removeNonVisibleChromeTabbedRecentEntries() { Set<Integer> visibleTaskIds = getTaskIdsForVisibleActivities(); Context context = ContextUtils.getApplicationContext(); ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); PackageManager pm = getPackageManager(); for (AppTask task : manager.getAppTasks()) { RecentTaskInfo info = DocumentUtils.getTaskInfoFromTask(task); if (info == null) continue; String className = DocumentUtils.getTaskClassName(task, pm); // It is not easily possible to distinguish between tasks sitting on top of // ChromeLauncherActivity, so we treat them all as likely ChromeTabbedActivities and // close them to be on the cautious side of things. if ((TextUtils.equals(className, ChromeTabbedActivity.class.getName()) || TextUtils.equals(className, ChromeLauncherActivity.class.getName())) && !visibleTaskIds.contains(info.id)) { task.finishAndRemoveTask(); } } }
/** * Finishes tasks other than the one with the given ID that were started with the given data * in the Intent, removing those tasks from Recents and leaving a unique task with the data. * @param data Passed in as part of the Intent's data when starting the Activity. * @param canonicalTaskId ID of the task will be the only one left with the ID. * @return Intent of one of the tasks that were finished. */ public static Intent finishOtherTasksWithData(Uri data, int canonicalTaskId) { if (data == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null; String dataString = data.toString(); Context context = ContextUtils.getApplicationContext(); ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.AppTask> tasksToFinish = new ArrayList<ActivityManager.AppTask>(); for (ActivityManager.AppTask task : manager.getAppTasks()) { RecentTaskInfo taskInfo = getTaskInfoFromTask(task); if (taskInfo == null) continue; int taskId = taskInfo.id; Intent baseIntent = taskInfo.baseIntent; String taskData = baseIntent == null ? null : taskInfo.baseIntent.getDataString(); if (TextUtils.equals(dataString, taskData) && (taskId == -1 || taskId != canonicalTaskId)) { tasksToFinish.add(task); } } return finishAndRemoveTasks(tasksToFinish); }
/** * Given an AppTask retrieves the task class name. * @param task The app task to use. * @param pm The package manager to use for resolving intent. * @return Fully qualified class name or null if we were not able to * determine it. */ public static String getTaskClassName(AppTask task, PackageManager pm) { RecentTaskInfo info = getTaskInfoFromTask(task); if (info == null) return null; Intent baseIntent = info.baseIntent; if (baseIntent == null) { return null; } else if (baseIntent.getComponent() != null) { return baseIntent.getComponent().getClassName(); } else { ResolveInfo resolveInfo = pm.resolveActivity(baseIntent, 0); if (resolveInfo == null) return null; return resolveInfo.activityInfo.name; } }
/** * Exclude the app from the recent tasks list. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void excludeFromTaskList() { ActivityManager am = (ActivityManager) getActivity() .getSystemService(Context.ACTIVITY_SERVICE); if (am == null || Build.VERSION.SDK_INT < 21) return; List<AppTask> tasks = am.getAppTasks(); if (tasks == null || tasks.isEmpty()) return; tasks.get(0).setExcludeFromRecents(true); }
/** * Figure out whether we need to restart the application after the tab migration is complete. * We don't need to restart if this is being accessed from FRE and no document activities have * been created yet. * @param optOut This is true when we are starting out in opted-out mode. * @return Whether to restart the application. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private boolean isRestartNeeded(boolean optOut) { if (optOut) return true; boolean isFromFre = getActivity().getIntent() != null && getActivity().getIntent().getBooleanExtra( IntentHandler.EXTRA_INVOKED_FROM_FRE, false); if (!isFromFre) return true; ActivityManager am = (ActivityManager) getActivity().getSystemService( Context.ACTIVITY_SERVICE); PackageManager pm = getActivity().getPackageManager(); List<AppTask> taskList = am.getAppTasks(); for (int i = 0; i < taskList.size(); i++) { String className = DocumentUtils.getTaskClassName(taskList.get(i), pm); if (className == null) continue; if (DocumentActivity.isDocumentActivity(className)) return true; } return false; }
/** * Finishes tasks other than the one with the given task ID that were started with the given * tabId, leaving a unique task to own a Tab with that particular ID. * @param tabId ID of the tab to remove duplicates for. * @param canonicalTaskId ID of the task will be the only one left with the ID. * @return Intent of one of the tasks that were finished. */ public static Intent finishOtherTasksWithTabID(int tabId, int canonicalTaskId) { if (tabId == Tab.INVALID_TAB_ID || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { return null; } Context context = ApplicationStatus.getApplicationContext(); ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.AppTask> tasksToFinish = new ArrayList<ActivityManager.AppTask>(); for (ActivityManager.AppTask task : manager.getAppTasks()) { RecentTaskInfo taskInfo = getTaskInfoFromTask(task); if (taskInfo == null) continue; int taskId = taskInfo.id; Intent baseIntent = taskInfo.baseIntent; int otherTabId = ActivityDelegate.getTabIdFromIntent(baseIntent); if (otherTabId == tabId && (taskId == -1 || taskId != canonicalTaskId)) { tasksToFinish.add(task); } } return finishAndRemoveTasks(tasksToFinish); }
/** * Finishes tasks other than the one with the given ID that were started with the given data * in the Intent, removing those tasks from Recents and leaving a unique task with the data. * @param data Passed in as part of the Intent's data when starting the Activity. * @param canonicalTaskId ID of the task will be the only one left with the ID. * @return Intent of one of the tasks that were finished. */ public static Intent finishOtherTasksWithData(Uri data, int canonicalTaskId) { if (data == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null; String dataString = data.toString(); Context context = ApplicationStatus.getApplicationContext(); ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.AppTask> tasksToFinish = new ArrayList<ActivityManager.AppTask>(); for (ActivityManager.AppTask task : manager.getAppTasks()) { RecentTaskInfo taskInfo = getTaskInfoFromTask(task); if (taskInfo == null) continue; int taskId = taskInfo.id; Intent baseIntent = taskInfo.baseIntent; String taskData = baseIntent == null ? null : taskInfo.baseIntent.getDataString(); if (TextUtils.equals(dataString, taskData) && (taskId == -1 || taskId != canonicalTaskId)) { tasksToFinish.add(task); } } return finishAndRemoveTasks(tasksToFinish); }
@TargetApi(Build.VERSION_CODES.LOLLIPOP) private boolean launchLastViewedActivity() { int tabId = ChromeApplication.getDocumentTabModelSelector().getCurrentTabId(); DocumentTabModel model = ChromeApplication.getDocumentTabModelSelector().getModelForTabId(tabId); if (tabId != Tab.INVALID_TAB_ID && model != null && relaunchTask(tabId)) { return true; } // Everything above failed, try to launch the last viewed activity based on app tasks list. ActivityManager am = (ActivityManager) getSystemService(Activity.ACTIVITY_SERVICE); PackageManager pm = getPackageManager(); for (AppTask task : am.getAppTasks()) { String className = DocumentUtils.getTaskClassName(task, pm); if (className == null || !DocumentActivity.isDocumentActivity(className)) continue; if (!moveToFront(task)) continue; return true; } return false; }
/** * Bring the task matching the given tab ID to the front. * @param tabId tab ID to search for. * @return Whether the task was successfully brought back. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private static boolean relaunchTask(int tabId) { if (tabId == Tab.INVALID_TAB_ID) return false; Context context = ApplicationStatus.getApplicationContext(); ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); for (AppTask task : manager.getAppTasks()) { RecentTaskInfo info = DocumentUtils.getTaskInfoFromTask(task); if (info == null) continue; int id = ActivityDelegate.getTabIdFromIntent(info.baseIntent); if (id != tabId) continue; DocumentTabModelSelector.setPrioritizedTabId(id); if (!moveToFront(task)) continue; return true; } return false; }
/** * On opting out, remove all the old tasks from the recents. * @param fromDocument Whether any possible migration was from document mode to classic. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void cleanUpChromeRecents(boolean fromDocument) { ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.AppTask> taskList = am.getAppTasks(); PackageManager pm = getPackageManager(); for (int i = 0; i < taskList.size(); i++) { AppTask task = taskList.get(i); String className = DocumentUtils.getTaskClassName(task, pm); if (className == null) continue; RecentTaskInfo taskInfo = DocumentUtils.getTaskInfoFromTask(task); if (taskInfo == null) continue; // Skip the document activities if we are migrating from classic to document. boolean skip = !fromDocument && DocumentActivity.isDocumentActivity(className); if (!skip && (taskInfo.id != getTaskId())) { taskList.get(i).finishAndRemoveTask(); } } }
@TargetApi(Build.VERSION_CODES.LOLLIPOP) private void removeNonVisibleChromeTabbedRecentEntries() { Set<Integer> visibleTaskIds = getTaskIdsForVisibleActivities(); Context context = ContextUtils.getApplicationContext(); ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); PackageManager pm = getPackageManager(); for (AppTask task : manager.getAppTasks()) { RecentTaskInfo info = DocumentUtils.getTaskInfoFromTask(task); if (info == null) continue; String className = DocumentUtils.getTaskClassName(task, pm); // It is not easily possible to distinguish between tasks sitting on top of // ChromeLauncherActivity, so we treat them all as likely ChromeTabbedActivities and // close them to be on the cautious side of things. if ((ChromeTabbedActivity.isTabbedModeClassName(className) || TextUtils.equals(className, ChromeLauncherActivity.class.getName())) && !visibleTaskIds.contains(info.id)) { task.finishAndRemoveTask(); } } }
@SuppressLint("NewApi") private boolean isMergedInstanceTaskRunning() { if (!FeatureUtilities.isTabModelMergingEnabled() || sMergedInstanceTaskId == 0) { return false; } ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); for (AppTask task : manager.getAppTasks()) { RecentTaskInfo info = DocumentUtils.getTaskInfoFromTask(task); if (info == null) continue; if (info.id == sMergedInstanceTaskId) return true; } return false; }
/** Returns a Set of Intents for all Chrome tasks currently known by the ActivityManager. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) protected Set<Intent> getBaseIntentsForAllTasks() { Set<Intent> baseIntents = new HashSet<Intent>(); Context context = ContextUtils.getApplicationContext(); ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); for (AppTask task : manager.getAppTasks()) { Intent intent = DocumentUtils.getBaseIntentFromTask(task); if (intent != null) baseIntents.add(intent); } return baseIntents; }
private static Intent finishAndRemoveTasks(List<ActivityManager.AppTask> tasksToFinish) { Intent removedIntent = null; for (ActivityManager.AppTask task : tasksToFinish) { Log.d(TAG, "Removing task with duplicated data: " + task); removedIntent = getBaseIntentFromTask(task); task.finishAndRemoveTask(); } return removedIntent; }
/** * Returns the RecentTaskInfo for the task, if the ActivityManager succeeds in finding the task. * @param task AppTask containing information about a task. * @return The RecentTaskInfo associated with the task, or null if it couldn't be found. */ public static RecentTaskInfo getTaskInfoFromTask(AppTask task) { RecentTaskInfo info = null; try { info = task.getTaskInfo(); } catch (IllegalArgumentException e) { Log.e(TAG, "Failed to retrieve task info: ", e); } return info; }
/** * Bring the task matching the given URL to the front if the task is retargetable. * @param incognito Whether or not the tab is incognito. * @param url URL that the tab would have been created for. If null, this param is ignored. * @return ID of the Tab if it was successfully relaunched, otherwise Tab.INVALID_TAB_ID. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private static int relaunchTask(boolean incognito, String url) { if (TextUtils.isEmpty(url)) return Tab.INVALID_TAB_ID; Context context = ApplicationStatus.getApplicationContext(); ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); for (AppTask task : manager.getAppTasks()) { RecentTaskInfo info = DocumentUtils.getTaskInfoFromTask(task); if (info == null) continue; String initialUrl = ActivityDelegate.getInitialUrlForDocument(info.baseIntent); if (TextUtils.isEmpty(initialUrl) || !TextUtils.equals(initialUrl, url)) continue; int id = ActivityDelegate.getTabIdFromIntent(info.baseIntent); DocumentTabModelSelector.setPrioritizedTabId(id); if (!ChromeApplication.getDocumentTabModelSelector().getModel(incognito) .isRetargetable(id)) { continue; } if (!moveToFront(task)) continue; return id; } return Tab.INVALID_TAB_ID; }
/** * Attempt to move a task back to the front. This can FAIL for some reason because the UID * of the DocumentActivity we try to bring back to the front doesn't match the * ChromeLauncherActivities. * @param task Task to attempt to bring back to the foreground. * @return Whether or not this succeeded. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private static boolean moveToFront(AppTask task) { try { task.moveToFront(); return true; } catch (SecurityException e) { sMoveToFrontExceptionHistogram.recordHit(); } return false; }
/** * 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 boolean shouldDestroyIncognitoProfile() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return false; Context context = ContextUtils.getApplicationContext(); ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); PackageManager pm = context.getPackageManager(); Set<Integer> tabbedModeTaskIds = new HashSet<>(); for (AppTask task : manager.getAppTasks()) { RecentTaskInfo info = DocumentUtils.getTaskInfoFromTask(task); if (info == null) continue; String className = DocumentUtils.getTaskClassName(task, pm); if (isTabbedModeClassName(className)) { tabbedModeTaskIds.add(info.id); } } if (tabbedModeTaskIds.size() == 0) { return Profile.getLastUsedProfile().hasOffTheRecordProfile(); } List<WeakReference<Activity>> activities = ApplicationStatus.getRunningActivities(); for (int i = 0; i < activities.size(); i++) { Activity activity = activities.get(i).get(); if (activity == null) continue; tabbedModeTaskIds.remove(activity.getTaskId()); } // If all tabbed mode tasks listed in Android recents are alive, check to see if // any have incognito tabs exist. If all are alive and no tabs exist, we should ensure that // we delete the incognito profile if one is around still. if (tabbedModeTaskIds.size() == 0) { return TabWindowManager.getInstance().canDestroyIncognitoProfile() && Profile.getLastUsedProfile().hasOffTheRecordProfile(); } // 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 proactivitely destroy the incognito profile. return false; }
/** * Returns the baseIntent of the RecentTaskInfo associated with the given task. * @param task Task to get the baseIntent for. * @return The baseIntent, or null if it couldn't be retrieved. */ public static Intent getBaseIntentFromTask(AppTask task) { RecentTaskInfo info = getTaskInfoFromTask(task); return info == null ? null : info.baseIntent; }