| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.systemui.recents.misc; |
| |
| import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; |
| import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; |
| import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; |
| import static android.app.ActivityManager.StackId.HOME_STACK_ID; |
| import static android.app.ActivityManager.StackId.PINNED_STACK_ID; |
| import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT; |
| |
| import android.app.ActivityManager; |
| import android.app.ActivityManagerNative; |
| import android.app.ActivityOptions; |
| import android.app.AppGlobals; |
| import android.app.IActivityManager; |
| import android.app.ITaskStackListener; |
| import android.app.UiModeManager; |
| import android.content.ComponentName; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ActivityInfo; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.IPackageManager; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.Paint; |
| import android.graphics.Point; |
| import android.graphics.PorterDuff; |
| import android.graphics.PorterDuffXfermode; |
| import android.graphics.Rect; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.graphics.drawable.ColorDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.os.Handler; |
| import android.os.IRemoteCallback; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.ParcelFileDescriptor; |
| import android.os.RemoteException; |
| import android.os.SystemProperties; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.provider.Settings; |
| import android.util.ArraySet; |
| import android.util.Log; |
| import android.util.MutableBoolean; |
| import android.view.Display; |
| import android.view.IAppTransitionAnimationSpecsFuture; |
| import android.view.IDockedStackListener; |
| import android.view.IWindowManager; |
| import android.view.WindowManager; |
| import android.view.WindowManager.KeyboardShortcutsReceiver; |
| import android.view.WindowManagerGlobal; |
| import android.view.accessibility.AccessibilityManager; |
| |
| import com.android.internal.app.AssistUtils; |
| import com.android.internal.os.BackgroundThread; |
| import com.android.systemui.R; |
| import com.android.systemui.recents.Recents; |
| import com.android.systemui.recents.RecentsDebugFlags; |
| import com.android.systemui.recents.RecentsImpl; |
| import com.android.systemui.recents.model.Task; |
| import com.android.systemui.recents.tv.RecentsTvImpl; |
| import com.android.systemui.recents.model.ThumbnailData; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Random; |
| |
| /** |
| * Acts as a shim around the real system services that we need to access data from, and provides |
| * a point of injection when testing UI. |
| */ |
| public class SystemServicesProxy { |
| final static String TAG = "SystemServicesProxy"; |
| |
| final static BitmapFactory.Options sBitmapOptions; |
| static { |
| sBitmapOptions = new BitmapFactory.Options(); |
| sBitmapOptions.inMutable = true; |
| sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; |
| } |
| |
| final static List<String> sRecentsBlacklist; |
| static { |
| sRecentsBlacklist = new ArrayList<>(); |
| sRecentsBlacklist.add("com.android.systemui.tv.pip.PipOnboardingActivity"); |
| sRecentsBlacklist.add("com.android.systemui.tv.pip.PipMenuActivity"); |
| } |
| |
| private static SystemServicesProxy sSystemServicesProxy; |
| |
| AccessibilityManager mAccm; |
| ActivityManager mAm; |
| IActivityManager mIam; |
| PackageManager mPm; |
| IPackageManager mIpm; |
| AssistUtils mAssistUtils; |
| WindowManager mWm; |
| IWindowManager mIwm; |
| UserManager mUm; |
| Display mDisplay; |
| String mRecentsPackage; |
| ComponentName mAssistComponent; |
| |
| boolean mIsSafeMode; |
| boolean mHasFreeformWorkspaceSupport; |
| |
| Bitmap mDummyIcon; |
| int mDummyThumbnailWidth; |
| int mDummyThumbnailHeight; |
| Paint mBgProtectionPaint; |
| Canvas mBgProtectionCanvas; |
| |
| private final Handler mHandler = new H(); |
| |
| /** |
| * An abstract class to track task stack changes. |
| * Classes should implement this instead of {@link android.app.ITaskStackListener} |
| * to reduce IPC calls from system services. These callbacks will be called on the main thread. |
| */ |
| public abstract static class TaskStackListener { |
| public void onTaskStackChanged() { } |
| public void onActivityPinned() { } |
| public void onPinnedActivityRestartAttempt() { } |
| public void onPinnedStackAnimationEnded() { } |
| public void onActivityForcedResizable(String packageName, int taskId) { } |
| public void onActivityDismissingDockedStack() { } |
| } |
| |
| /** |
| * Implementation of {@link android.app.ITaskStackListener} to listen task stack changes from |
| * ActivityManagerNative. |
| * This simply passes callbacks to listeners through {@link H}. |
| * */ |
| private ITaskStackListener.Stub mTaskStackListener = new ITaskStackListener.Stub() { |
| @Override |
| public void onTaskStackChanged() throws RemoteException { |
| mHandler.removeMessages(H.ON_TASK_STACK_CHANGED); |
| mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED); |
| } |
| |
| @Override |
| public void onActivityPinned() throws RemoteException { |
| mHandler.removeMessages(H.ON_ACTIVITY_PINNED); |
| mHandler.sendEmptyMessage(H.ON_ACTIVITY_PINNED); |
| } |
| |
| @Override |
| public void onPinnedActivityRestartAttempt() throws RemoteException{ |
| mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT); |
| mHandler.sendEmptyMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT); |
| } |
| |
| @Override |
| public void onPinnedStackAnimationEnded() throws RemoteException { |
| mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED); |
| mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED); |
| } |
| |
| @Override |
| public void onActivityForcedResizable(String packageName, int taskId) |
| throws RemoteException { |
| mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, 0, packageName) |
| .sendToTarget(); |
| } |
| |
| @Override |
| public void onActivityDismissingDockedStack() throws RemoteException { |
| mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK); |
| } |
| }; |
| |
| /** |
| * List of {@link TaskStackListener} registered from {@link #registerTaskStackListener}. |
| */ |
| private List<TaskStackListener> mTaskStackListeners = new ArrayList<>(); |
| |
| /** Private constructor */ |
| private SystemServicesProxy(Context context) { |
| mAccm = AccessibilityManager.getInstance(context); |
| mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); |
| mIam = ActivityManagerNative.getDefault(); |
| mPm = context.getPackageManager(); |
| mIpm = AppGlobals.getPackageManager(); |
| mAssistUtils = new AssistUtils(context); |
| mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); |
| mIwm = WindowManagerGlobal.getWindowManagerService(); |
| mUm = UserManager.get(context); |
| mDisplay = mWm.getDefaultDisplay(); |
| mRecentsPackage = context.getPackageName(); |
| mHasFreeformWorkspaceSupport = |
| mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT) || |
| Settings.Global.getInt(context.getContentResolver(), |
| DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0; |
| mIsSafeMode = mPm.isSafeMode(); |
| |
| // Get the dummy thumbnail width/heights |
| Resources res = context.getResources(); |
| int wId = com.android.internal.R.dimen.thumbnail_width; |
| int hId = com.android.internal.R.dimen.thumbnail_height; |
| mDummyThumbnailWidth = res.getDimensionPixelSize(wId); |
| mDummyThumbnailHeight = res.getDimensionPixelSize(hId); |
| |
| // Create the protection paints |
| mBgProtectionPaint = new Paint(); |
| mBgProtectionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)); |
| mBgProtectionPaint.setColor(0xFFffffff); |
| mBgProtectionCanvas = new Canvas(); |
| |
| // Resolve the assist intent |
| mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.myUserId()); |
| |
| if (RecentsDebugFlags.Static.EnableMockTasks) { |
| // Create a dummy icon |
| mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); |
| mDummyIcon.eraseColor(0xFF999999); |
| } |
| |
| UiModeManager uiModeManager = (UiModeManager) context. |
| getSystemService(Context.UI_MODE_SERVICE); |
| if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { |
| Collections.addAll(sRecentsBlacklist, |
| res.getStringArray(R.array.recents_tv_blacklist_array)); |
| } else { |
| Collections.addAll(sRecentsBlacklist, |
| res.getStringArray(R.array.recents_blacklist_array)); |
| } |
| } |
| |
| /** |
| * Returns the single instance of the {@link SystemServicesProxy}. |
| * This should only be called on the main thread. |
| */ |
| public static SystemServicesProxy getInstance(Context context) { |
| if (!Looper.getMainLooper().isCurrentThread()) { |
| throw new RuntimeException("Must be called on the UI thread"); |
| } |
| if (sSystemServicesProxy == null) { |
| sSystemServicesProxy = new SystemServicesProxy(context); |
| } |
| return sSystemServicesProxy; |
| } |
| |
| /** |
| * @return whether the provided {@param className} is blacklisted |
| */ |
| public boolean isBlackListedActivity(String className) { |
| return sRecentsBlacklist.contains(className); |
| } |
| |
| /** |
| * Returns a list of the recents tasks. |
| * |
| * @param includeFrontMostExcludedTask if set, will ensure that the front most excluded task |
| * will be visible, otherwise no excluded tasks will be |
| * visible. |
| */ |
| public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId, |
| boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) { |
| if (mAm == null) return null; |
| |
| // If we are mocking, then create some recent tasks |
| if (RecentsDebugFlags.Static.EnableMockTasks) { |
| ArrayList<ActivityManager.RecentTaskInfo> tasks = |
| new ArrayList<ActivityManager.RecentTaskInfo>(); |
| int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.MockTaskCount); |
| for (int i = 0; i < count; i++) { |
| // Create a dummy component name |
| int packageIndex = i % RecentsDebugFlags.Static.MockTasksPackageCount; |
| ComponentName cn = new ComponentName("com.android.test" + packageIndex, |
| "com.android.test" + i + ".Activity"); |
| String description = "" + i + " - " + |
| Long.toString(Math.abs(new Random().nextLong()), 36); |
| // Create the recent task info |
| ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); |
| rti.id = rti.persistentId = rti.affiliatedTaskId = i; |
| rti.baseIntent = new Intent(); |
| rti.baseIntent.setComponent(cn); |
| rti.description = description; |
| rti.firstActiveTime = rti.lastActiveTime = i; |
| if (i % 2 == 0) { |
| rti.taskDescription = new ActivityManager.TaskDescription(description, |
| Bitmap.createBitmap(mDummyIcon), null, |
| 0xFF000000 | (0xFFFFFF & new Random().nextInt()), |
| 0xFF000000 | (0xFFFFFF & new Random().nextInt())); |
| } else { |
| rti.taskDescription = new ActivityManager.TaskDescription(); |
| } |
| tasks.add(rti); |
| } |
| return tasks; |
| } |
| |
| // Remove home/recents/excluded tasks |
| int minNumTasksToQuery = 10; |
| int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks); |
| int flags = ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS | |
| ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK | |
| ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS | |
| ActivityManager.RECENT_IGNORE_UNAVAILABLE | |
| ActivityManager.RECENT_INCLUDE_PROFILES; |
| if (includeFrontMostExcludedTask) { |
| flags |= ActivityManager.RECENT_WITH_EXCLUDED; |
| } |
| List<ActivityManager.RecentTaskInfo> tasks = null; |
| try { |
| tasks = mAm.getRecentTasksForUser(numTasksToQuery, flags, userId); |
| } catch (Exception e) { |
| Log.e(TAG, "Failed to get recent tasks", e); |
| } |
| |
| // Break early if we can't get a valid set of tasks |
| if (tasks == null) { |
| return new ArrayList<>(); |
| } |
| |
| boolean isFirstValidTask = true; |
| Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator(); |
| while (iter.hasNext()) { |
| ActivityManager.RecentTaskInfo t = iter.next(); |
| |
| // NOTE: The order of these checks happens in the expected order of the traversal of the |
| // tasks |
| |
| // Remove the task if it or it's package are blacklsited |
| if (sRecentsBlacklist.contains(t.realActivity.getClassName()) || |
| sRecentsBlacklist.contains(t.realActivity.getPackageName())) { |
| iter.remove(); |
| continue; |
| } |
| |
| // Remove the task if it is marked as excluded, unless it is the first most task and we |
| // are requested to include it |
| boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) |
| == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; |
| isExcluded |= quietProfileIds.contains(t.userId); |
| if (isExcluded && (!isFirstValidTask || !includeFrontMostExcludedTask)) { |
| iter.remove(); |
| } |
| |
| isFirstValidTask = false; |
| } |
| |
| return tasks.subList(0, Math.min(tasks.size(), numLatestTasks)); |
| } |
| |
| /** |
| * Returns the top running task. |
| */ |
| public ActivityManager.RunningTaskInfo getRunningTask() { |
| List<ActivityManager.RunningTaskInfo> tasks = mAm.getRunningTasks(1); |
| if (tasks != null && !tasks.isEmpty()) { |
| return tasks.get(0); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns whether the recents activity is currently visible. |
| */ |
| public boolean isRecentsActivityVisible() { |
| return isRecentsActivityVisible(null); |
| } |
| |
| /** |
| * Returns whether the recents activity is currently visible. |
| * |
| * @param isHomeStackVisible if provided, will return whether the home stack is visible |
| * regardless of the recents visibility |
| */ |
| public boolean isRecentsActivityVisible(MutableBoolean isHomeStackVisible) { |
| if (mIam == null) return false; |
| |
| try { |
| ActivityManager.StackInfo stackInfo = mIam.getStackInfo( |
| ActivityManager.StackId.HOME_STACK_ID); |
| ActivityManager.StackInfo fullscreenStackInfo = mIam.getStackInfo( |
| ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID); |
| ComponentName topActivity = stackInfo.topActivity; |
| boolean homeStackVisibleNotOccluded = stackInfo.visible; |
| if (fullscreenStackInfo != null) { |
| boolean isFullscreenStackOccludingHome = fullscreenStackInfo.visible && |
| fullscreenStackInfo.position > stackInfo.position; |
| homeStackVisibleNotOccluded &= !isFullscreenStackOccludingHome; |
| } |
| if (isHomeStackVisible != null) { |
| isHomeStackVisible.value = homeStackVisibleNotOccluded; |
| } |
| return (homeStackVisibleNotOccluded && topActivity != null |
| && topActivity.getPackageName().equals(RecentsImpl.RECENTS_PACKAGE) |
| && Recents.RECENTS_ACTIVITIES.contains(topActivity.getClassName())); |
| } catch (RemoteException e) { |
| e.printStackTrace(); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns whether this device has freeform workspaces. |
| */ |
| public boolean hasFreeformWorkspaceSupport() { |
| return mHasFreeformWorkspaceSupport; |
| } |
| |
| /** |
| * Returns whether this device is in the safe mode. |
| */ |
| public boolean isInSafeMode() { |
| return mIsSafeMode; |
| } |
| |
| /** Docks a task to the side of the screen and starts it. */ |
| public boolean startTaskInDockedMode(int taskId, int createMode) { |
| if (mIam == null) return false; |
| |
| try { |
| final ActivityOptions options = ActivityOptions.makeBasic(); |
| options.setDockCreateMode(createMode); |
| options.setLaunchStackId(DOCKED_STACK_ID); |
| mIam.startActivityFromRecents(taskId, options.toBundle()); |
| return true; |
| } catch (Exception e) { |
| Log.e(TAG, "Failed to dock task: " + taskId + " with createMode: " + createMode, e); |
| } |
| return false; |
| } |
| |
| /** Docks an already resumed task to the side of the screen. */ |
| public boolean moveTaskToDockedStack(int taskId, int createMode, Rect initialBounds) { |
| if (mIam == null) { |
| return false; |
| } |
| |
| try { |
| return mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */, |
| false /* animate */, initialBounds, true /* moveHomeStackFront */ ); |
| } catch (RemoteException e) { |
| e.printStackTrace(); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns whether the given stack id is the home stack id. |
| */ |
| public static boolean isHomeStack(int stackId) { |
| return stackId == HOME_STACK_ID; |
| } |
| |
| /** |
| * Returns whether the given stack id is the pinned stack id. |
| */ |
| public static boolean isPinnedStack(int stackId){ |
| return stackId == PINNED_STACK_ID; |
| } |
| |
| /** |
| * Returns whether the given stack id is the docked stack id. |
| */ |
| public static boolean isDockedStack(int stackId) { |
| return stackId == DOCKED_STACK_ID; |
| } |
| |
| /** |
| * Returns whether the given stack id is the freeform workspace stack id. |
| */ |
| public static boolean isFreeformStack(int stackId) { |
| return stackId == FREEFORM_WORKSPACE_STACK_ID; |
| } |
| |
| /** |
| * @return whether there are any docked tasks for the current user. |
| */ |
| public boolean hasDockedTask() { |
| if (mIam == null) return false; |
| |
| ActivityManager.StackInfo stackInfo = null; |
| try { |
| stackInfo = mIam.getStackInfo(DOCKED_STACK_ID); |
| } catch (RemoteException e) { |
| e.printStackTrace(); |
| } |
| |
| if (stackInfo != null) { |
| int userId = getCurrentUser(); |
| boolean hasUserTask = false; |
| for (int i = stackInfo.taskUserIds.length - 1; i >= 0 && !hasUserTask; i--) { |
| hasUserTask = (stackInfo.taskUserIds[i] == userId); |
| } |
| return hasUserTask; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns whether there is a soft nav bar. |
| */ |
| public boolean hasSoftNavigationBar() { |
| try { |
| return WindowManagerGlobal.getWindowManagerService().hasNavigationBar(); |
| } catch (RemoteException e) { |
| e.printStackTrace(); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns whether the device has a transposed nav bar (on the right of the screen) in the |
| * current display orientation. |
| */ |
| public boolean hasTransposedNavigationBar() { |
| Rect insets = new Rect(); |
| getStableInsets(insets); |
| return insets.right > 0; |
| } |
| |
| /** |
| * Cancels the current window transtion to/from Recents for the given task id. |
| */ |
| public void cancelWindowTransition(int taskId) { |
| if (mWm == null) return; |
| |
| try { |
| WindowManagerGlobal.getWindowManagerService().cancelTaskWindowTransition(taskId); |
| } catch (RemoteException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * Cancels the current thumbnail transtion to/from Recents for the given task id. |
| */ |
| public void cancelThumbnailTransition(int taskId) { |
| if (mWm == null) return; |
| |
| try { |
| WindowManagerGlobal.getWindowManagerService().cancelTaskThumbnailTransition(taskId); |
| } catch (RemoteException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** Returns the top task thumbnail for the given task id */ |
| public ThumbnailData getTaskThumbnail(int taskId) { |
| if (mAm == null) return null; |
| ThumbnailData thumbnailData = new ThumbnailData(); |
| |
| // If we are mocking, then just return a dummy thumbnail |
| if (RecentsDebugFlags.Static.EnableMockTasks) { |
| thumbnailData.thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth, |
| mDummyThumbnailHeight, Bitmap.Config.ARGB_8888); |
| thumbnailData.thumbnail.eraseColor(0xff333333); |
| return thumbnailData; |
| } |
| |
| getThumbnail(taskId, thumbnailData); |
| if (thumbnailData.thumbnail != null) { |
| thumbnailData.thumbnail.setHasAlpha(false); |
| // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top |
| // left pixel, then assume the whole thumbnail is transparent. Generally, proper |
| // screenshots are always composed onto a bitmap that has no alpha. |
| if (Color.alpha(thumbnailData.thumbnail.getPixel(0, 0)) == 0) { |
| mBgProtectionCanvas.setBitmap(thumbnailData.thumbnail); |
| mBgProtectionCanvas.drawRect(0, 0, thumbnailData.thumbnail.getWidth(), |
| thumbnailData.thumbnail.getHeight(), mBgProtectionPaint); |
| mBgProtectionCanvas.setBitmap(null); |
| Log.e(TAG, "Invalid screenshot detected from getTaskThumbnail()"); |
| } |
| } |
| return thumbnailData; |
| } |
| |
| /** |
| * Returns a task thumbnail from the activity manager |
| */ |
| public void getThumbnail(int taskId, ThumbnailData thumbnailDataOut) { |
| if (mAm == null) { |
| return; |
| } |
| |
| ActivityManager.TaskThumbnail taskThumbnail = mAm.getTaskThumbnail(taskId); |
| if (taskThumbnail == null) { |
| return; |
| } |
| |
| Bitmap thumbnail = taskThumbnail.mainThumbnail; |
| ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor; |
| if (thumbnail == null && descriptor != null) { |
| thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor(), |
| null, sBitmapOptions); |
| } |
| if (descriptor != null) { |
| try { |
| descriptor.close(); |
| } catch (IOException e) { |
| } |
| } |
| thumbnailDataOut.thumbnail = thumbnail; |
| thumbnailDataOut.thumbnailInfo = taskThumbnail.thumbnailInfo; |
| } |
| |
| /** |
| * Moves a task into another stack. |
| */ |
| public void moveTaskToStack(int taskId, int stackId) { |
| if (mIam == null) return; |
| |
| try { |
| mIam.positionTaskInStack(taskId, stackId, 0); |
| } catch (RemoteException | IllegalArgumentException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** Removes the task */ |
| public void removeTask(final int taskId) { |
| if (mAm == null) return; |
| if (RecentsDebugFlags.Static.EnableMockTasks) return; |
| |
| // Remove the task. |
| BackgroundThread.getHandler().post(new Runnable() { |
| @Override |
| public void run() { |
| mAm.removeTask(taskId); |
| } |
| }); |
| } |
| |
| /** |
| * Sends a message to close other system windows. |
| */ |
| public void sendCloseSystemWindows(String reason) { |
| if (ActivityManagerNative.isSystemReady()) { |
| try { |
| mIam.closeSystemDialogs(reason); |
| } catch (RemoteException e) { |
| } |
| } |
| } |
| |
| /** |
| * Returns the activity info for a given component name. |
| * |
| * @param cn The component name of the activity. |
| * @param userId The userId of the user that this is for. |
| */ |
| public ActivityInfo getActivityInfo(ComponentName cn, int userId) { |
| if (mIpm == null) return null; |
| if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo(); |
| |
| try { |
| return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId); |
| } catch (RemoteException e) { |
| e.printStackTrace(); |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the activity info for a given component name. |
| * |
| * @param cn The component name of the activity. |
| */ |
| public ActivityInfo getActivityInfo(ComponentName cn) { |
| if (mPm == null) return null; |
| if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo(); |
| |
| try { |
| return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA); |
| } catch (PackageManager.NameNotFoundException e) { |
| e.printStackTrace(); |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the activity label, badging if necessary. |
| */ |
| public String getBadgedActivityLabel(ActivityInfo info, int userId) { |
| if (mPm == null) return null; |
| |
| // If we are mocking, then return a mock label |
| if (RecentsDebugFlags.Static.EnableMockTasks) { |
| return "Recent Task: " + userId; |
| } |
| |
| return getBadgedLabel(info.loadLabel(mPm).toString(), userId); |
| } |
| |
| /** |
| * Returns the application label, badging if necessary. |
| */ |
| public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) { |
| if (mPm == null) return null; |
| |
| // If we are mocking, then return a mock label |
| if (RecentsDebugFlags.Static.EnableMockTasks) { |
| return "Recent Task App: " + userId; |
| } |
| |
| return getBadgedLabel(appInfo.loadLabel(mPm).toString(), userId); |
| } |
| |
| /** |
| * Returns the content description for a given task, badging it if necessary. The content |
| * description joins the app and activity labels. |
| */ |
| public String getBadgedContentDescription(ActivityInfo info, int userId, Resources res) { |
| // If we are mocking, then return a mock label |
| if (RecentsDebugFlags.Static.EnableMockTasks) { |
| return "Recent Task Content Description: " + userId; |
| } |
| |
| String activityLabel = info.loadLabel(mPm).toString(); |
| String applicationLabel = info.applicationInfo.loadLabel(mPm).toString(); |
| String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId); |
| return applicationLabel.equals(activityLabel) ? badgedApplicationLabel |
| : res.getString(R.string.accessibility_recents_task_header, |
| badgedApplicationLabel, activityLabel); |
| } |
| |
| /** |
| * Returns the activity icon for the ActivityInfo for a user, badging if |
| * necessary. |
| */ |
| public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) { |
| if (mPm == null) return null; |
| |
| // If we are mocking, then return a mock label |
| if (RecentsDebugFlags.Static.EnableMockTasks) { |
| return new ColorDrawable(0xFF666666); |
| } |
| |
| Drawable icon = info.loadIcon(mPm); |
| return getBadgedIcon(icon, userId); |
| } |
| |
| /** |
| * Returns the application icon for the ApplicationInfo for a user, badging if |
| * necessary. |
| */ |
| public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) { |
| if (mPm == null) return null; |
| |
| // If we are mocking, then return a mock label |
| if (RecentsDebugFlags.Static.EnableMockTasks) { |
| return new ColorDrawable(0xFF666666); |
| } |
| |
| Drawable icon = appInfo.loadIcon(mPm); |
| return getBadgedIcon(icon, userId); |
| } |
| |
| /** |
| * Returns the task description icon, loading and badging it if it necessary. |
| */ |
| public Drawable getBadgedTaskDescriptionIcon(ActivityManager.TaskDescription taskDescription, |
| int userId, Resources res) { |
| |
| // If we are mocking, then return a mock label |
| if (RecentsDebugFlags.Static.EnableMockTasks) { |
| return new ColorDrawable(0xFF666666); |
| } |
| |
| Bitmap tdIcon = taskDescription.getInMemoryIcon(); |
| if (tdIcon == null) { |
| tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon( |
| taskDescription.getIconFilename(), userId); |
| } |
| if (tdIcon != null) { |
| return getBadgedIcon(new BitmapDrawable(res, tdIcon), userId); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the given icon for a user, badging if necessary. |
| */ |
| private Drawable getBadgedIcon(Drawable icon, int userId) { |
| if (userId != UserHandle.myUserId()) { |
| icon = mPm.getUserBadgedIcon(icon, new UserHandle(userId)); |
| } |
| return icon; |
| } |
| |
| /** |
| * Returns a banner used on TV for the specified Activity. |
| */ |
| public Drawable getActivityBanner(ActivityInfo info) { |
| if (mPm == null) return null; |
| |
| // If we are mocking, then return a mock banner |
| if (RecentsDebugFlags.Static.EnableMockTasks) { |
| return new ColorDrawable(0xFF666666); |
| } |
| |
| Drawable banner = info.loadBanner(mPm); |
| return banner; |
| } |
| |
| /** |
| * Returns a logo used on TV for the specified Activity. |
| */ |
| public Drawable getActivityLogo(ActivityInfo info) { |
| if (mPm == null) return null; |
| |
| // If we are mocking, then return a mock logo |
| if (RecentsDebugFlags.Static.EnableMockTasks) { |
| return new ColorDrawable(0xFF666666); |
| } |
| |
| Drawable logo = info.loadLogo(mPm); |
| return logo; |
| } |
| |
| |
| /** |
| * Returns the given label for a user, badging if necessary. |
| */ |
| private String getBadgedLabel(String label, int userId) { |
| if (userId != UserHandle.myUserId()) { |
| label = mPm.getUserBadgedLabel(label, new UserHandle(userId)).toString(); |
| } |
| return label; |
| } |
| |
| /** Returns the package name of the home activity. */ |
| public String getHomeActivityPackageName() { |
| if (mPm == null) return null; |
| if (RecentsDebugFlags.Static.EnableMockTasks) return null; |
| |
| ArrayList<ResolveInfo> homeActivities = new ArrayList<>(); |
| ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities); |
| if (defaultHomeActivity != null) { |
| return defaultHomeActivity.getPackageName(); |
| } else if (homeActivities.size() == 1) { |
| ResolveInfo info = homeActivities.get(0); |
| if (info.activityInfo != null) { |
| return info.activityInfo.packageName; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns whether the provided {@param userId} represents the system user. |
| */ |
| public boolean isSystemUser(int userId) { |
| return userId == UserHandle.USER_SYSTEM; |
| } |
| |
| /** |
| * Returns the current user id. |
| */ |
| public int getCurrentUser() { |
| if (mAm == null) return 0; |
| |
| return mAm.getCurrentUser(); |
| } |
| |
| /** |
| * Returns the processes user id. |
| */ |
| public int getProcessUser() { |
| if (mUm == null) return 0; |
| return mUm.getUserHandle(); |
| } |
| |
| /** |
| * Returns whether touch exploration is currently enabled. |
| */ |
| public boolean isTouchExplorationEnabled() { |
| if (mAccm == null) return false; |
| |
| return mAccm.isEnabled() && mAccm.isTouchExplorationEnabled(); |
| } |
| |
| /** |
| * Returns whether the current task is in screen-pinning mode. |
| */ |
| public boolean isScreenPinningActive() { |
| if (mIam == null) return false; |
| |
| try { |
| return mIam.isInLockTaskMode(); |
| } catch (RemoteException e) { |
| return false; |
| } |
| } |
| |
| /** |
| * Returns a global setting. |
| */ |
| public int getGlobalSetting(Context context, String setting) { |
| ContentResolver cr = context.getContentResolver(); |
| return Settings.Global.getInt(cr, setting, 0); |
| } |
| |
| /** |
| * Returns a system setting. |
| */ |
| public int getSystemSetting(Context context, String setting) { |
| ContentResolver cr = context.getContentResolver(); |
| return Settings.System.getInt(cr, setting, 0); |
| } |
| |
| /** |
| * Returns a system property. |
| */ |
| public String getSystemProperty(String key) { |
| return SystemProperties.get(key); |
| } |
| |
| /** |
| * Returns the smallest width/height. |
| */ |
| public int getDeviceSmallestWidth() { |
| if (mDisplay == null) return 0; |
| |
| Point smallestSizeRange = new Point(); |
| Point largestSizeRange = new Point(); |
| mDisplay.getCurrentSizeRange(smallestSizeRange, largestSizeRange); |
| return smallestSizeRange.x; |
| } |
| |
| /** |
| * Returns the current display rect in the current display orientation. |
| */ |
| public Rect getDisplayRect() { |
| Rect displayRect = new Rect(); |
| if (mDisplay == null) return displayRect; |
| |
| Point p = new Point(); |
| mDisplay.getRealSize(p); |
| displayRect.set(0, 0, p.x, p.y); |
| return displayRect; |
| } |
| |
| /** |
| * Returns the window rect for the RecentsActivity, based on the dimensions of the home stack. |
| */ |
| public Rect getWindowRect() { |
| Rect windowRect = new Rect(); |
| if (mIam == null) return windowRect; |
| |
| try { |
| // Use the home stack bounds |
| ActivityManager.StackInfo stackInfo = mIam.getStackInfo(HOME_STACK_ID); |
| if (stackInfo != null) { |
| windowRect.set(stackInfo.bounds); |
| } |
| } catch (RemoteException e) { |
| e.printStackTrace(); |
| } finally { |
| return windowRect; |
| } |
| } |
| |
| /** Starts an activity from recents. */ |
| public boolean startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName, |
| ActivityOptions options) { |
| if (mIam != null) { |
| try { |
| if (taskKey.stackId == DOCKED_STACK_ID) { |
| // We show non-visible docked tasks in Recents, but we always want to launch |
| // them in the fullscreen stack. |
| if (options == null) { |
| options = ActivityOptions.makeBasic(); |
| } |
| options.setLaunchStackId(FULLSCREEN_WORKSPACE_STACK_ID); |
| } |
| mIam.startActivityFromRecents( |
| taskKey.id, options == null ? null : options.toBundle()); |
| return true; |
| } catch (Exception e) { |
| Log.e(TAG, context.getString(R.string.recents_launch_error_message, taskName), e); |
| } |
| } |
| return false; |
| } |
| |
| /** Starts an in-place animation on the front most application windows. */ |
| public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) { |
| if (mIam == null) return; |
| |
| try { |
| mIam.startInPlaceAnimationOnFrontMostApplication(opts); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * Registers a task stack listener with the system. |
| * This should be called on the main thread. |
| */ |
| public void registerTaskStackListener(TaskStackListener listener) { |
| if (mIam == null) return; |
| |
| mTaskStackListeners.add(listener); |
| if (mTaskStackListeners.size() == 1) { |
| // Register mTaskStackListener to IActivityManager only once if needed. |
| try { |
| mIam.registerTaskStackListener(mTaskStackListener); |
| } catch (Exception e) { |
| Log.w(TAG, "Failed to call registerTaskStackListener", e); |
| } |
| } |
| } |
| |
| public void endProlongedAnimations() { |
| if (mWm == null) { |
| return; |
| } |
| try { |
| WindowManagerGlobal.getWindowManagerService().endProlongedAnimations(); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| public void registerDockedStackListener(IDockedStackListener listener) { |
| if (mWm == null) return; |
| |
| try { |
| WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(listener); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * Calculates the size of the dock divider in the current orientation. |
| */ |
| public int getDockedDividerSize(Context context) { |
| Resources res = context.getResources(); |
| int dividerWindowWidth = res.getDimensionPixelSize( |
| com.android.internal.R.dimen.docked_stack_divider_thickness); |
| int dividerInsets = res.getDimensionPixelSize( |
| com.android.internal.R.dimen.docked_stack_divider_insets); |
| return dividerWindowWidth - 2 * dividerInsets; |
| } |
| |
| public void requestKeyboardShortcuts( |
| Context context, KeyboardShortcutsReceiver receiver, int deviceId) { |
| mWm.requestAppKeyboardShortcuts(receiver, deviceId); |
| } |
| |
| public void getStableInsets(Rect outStableInsets) { |
| if (mWm == null) return; |
| |
| try { |
| WindowManagerGlobal.getWindowManagerService().getStableInsets(outStableInsets); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| public void overridePendingAppTransitionMultiThumbFuture( |
| IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener, |
| boolean scaleUp) { |
| try { |
| WindowManagerGlobal.getWindowManagerService() |
| .overridePendingAppTransitionMultiThumbFuture(future, animStartedListener, |
| scaleUp); |
| } catch (RemoteException e) { |
| Log.w(TAG, "Failed to override transition: " + e); |
| } |
| } |
| |
| /** |
| * Updates the visibility of recents. |
| */ |
| public void setRecentsVisibility(boolean visible) { |
| try { |
| mIwm.setRecentsVisibility(visible); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Unable to reach window manager", e); |
| } |
| } |
| |
| /** |
| * Updates the visibility of the picture-in-picture. |
| */ |
| public void setTvPipVisibility(boolean visible) { |
| try { |
| mIwm.setTvPipVisibility(visible); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Unable to reach window manager", e); |
| } |
| } |
| |
| private final class H extends Handler { |
| private static final int ON_TASK_STACK_CHANGED = 1; |
| private static final int ON_ACTIVITY_PINNED = 2; |
| private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 3; |
| private static final int ON_PINNED_STACK_ANIMATION_ENDED = 4; |
| private static final int ON_ACTIVITY_FORCED_RESIZABLE = 5; |
| private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 6; |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case ON_TASK_STACK_CHANGED: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onTaskStackChanged(); |
| } |
| break; |
| } |
| case ON_ACTIVITY_PINNED: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onActivityPinned(); |
| } |
| break; |
| } |
| case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onPinnedActivityRestartAttempt(); |
| } |
| break; |
| } |
| case ON_PINNED_STACK_ANIMATION_ENDED: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onPinnedStackAnimationEnded(); |
| } |
| break; |
| } |
| case ON_ACTIVITY_FORCED_RESIZABLE: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onActivityForcedResizable( |
| (String) msg.obj, msg.arg1); |
| } |
| break; |
| } |
| case ON_ACTIVITY_DISMISSING_DOCKED_STACK: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onActivityDismissingDockedStack(); |
| } |
| break; |
| } |
| } |
| } |
| } |
| } |