diff options
Diffstat (limited to 'packages/SystemUI/shared/src')
44 files changed, 5434 insertions, 0 deletions
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl new file mode 100644 index 000000000000..7f382acb0aab --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 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.shared.recents; + +import android.view.MotionEvent; +import com.android.systemui.shared.recents.ISystemUiProxy; + +oneway interface IOverviewProxy { + void onBind(in ISystemUiProxy sysUiProxy); + + /** + * Proxies motion events from the nav bar in SystemUI to the OverviewProxyService. The sender + * guarantees the following order of events: + * + * Normal gesture: DOWN, (MOVE/POINTER_DOWN/POINTER_UP)*, UP + * Quick switch: DOWN, (MOVE/POINTER_DOWN/POINTER_UP)*, SWITCH + * Quick scrub: DOWN, (MOVE/POINTER_DOWN/POINTER_UP)*, SCRUB_START, SCRUB_PROGRESS*, SCRUB_END + * + * Once quick switch/scrub is sent, then no further motion events will be provided. + */ + void onMotionEvent(in MotionEvent event); + + /** + * Sent when a user has quickly flinged on the nav bar to switch tasks. Once this event is sent + * the caller will stop sending any motion events and will no longer preemptively cancel any + * recents animations started as a part of the motion event handling. + */ + void onQuickSwitch(); + + /** + * Sent when the user starts to actively scrub the nav bar to switch tasks. Once this event is + * sent the caller will stop sending any motion events and will no longer preemptively cancel + * any recents animations started as a part of the motion event handling. + */ + void onQuickScrubStart(); + + /** + * Sent when the user stops actively scrubbing the nav bar to switch tasks. + */ + void onQuickScrubEnd(); + + /** + * Sent for each movement over the nav bar while the user is scrubbing it to switch tasks. + */ + void onQuickScrubProgress(float progress); +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl new file mode 100644 index 000000000000..4cf817e02fff --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 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.shared.recents; + +import android.graphics.Rect; +import com.android.systemui.shared.system.GraphicBufferCompat; + +/** + * Temporary callbacks into SystemUI. + */ +interface ISystemUiProxy { + + /** + * Proxies SurfaceControl.screenshotToBuffer(). + */ + GraphicBufferCompat screenshot(in Rect sourceCrop, int width, int height, int minLayer, + int maxLayer, boolean useIdentityTransform, int rotation); + + /** + * Begins screen pinning on the provided {@param taskId}. + */ + void startScreenPinning(int taskId); + + /** + * Called when the overview service has started the recents animation. + */ + void onRecentsAnimationStarted(); + + /** + * Specifies the text to be shown for onboarding the new swipe-up gesture to access recents. + */ + void setRecentsOnboardingText(CharSequence text); +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java new file mode 100644 index 000000000000..ddd27b0b38ba --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2017 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.shared.recents.model; + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.HandlerThread; +import android.util.Log; + +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.PackageManagerWrapper; + +/** + * Background task resource loader + */ +class BackgroundTaskLoader implements Runnable { + static String TAG = "BackgroundTaskLoader"; + static boolean DEBUG = false; + + private Context mContext; + private final HandlerThread mLoadThread; + private final Handler mLoadThreadHandler; + private final Handler mMainThreadHandler; + + private final TaskResourceLoadQueue mLoadQueue; + private final TaskKeyLruCache<Drawable> mIconCache; + private final BitmapDrawable mDefaultIcon; + + private boolean mStarted; + private boolean mCancelled; + private boolean mWaitingOnLoadQueue; + + private final OnIdleChangedListener mOnIdleChangedListener; + + /** Constructor, creates a new loading thread that loads task resources in the background */ + public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue, + TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon, + OnIdleChangedListener onIdleChangedListener) { + mLoadQueue = loadQueue; + mIconCache = iconCache; + mDefaultIcon = defaultIcon; + mMainThreadHandler = new Handler(); + mOnIdleChangedListener = onIdleChangedListener; + mLoadThread = new HandlerThread("Recents-TaskResourceLoader", + android.os.Process.THREAD_PRIORITY_BACKGROUND); + mLoadThread.start(); + mLoadThreadHandler = new Handler(mLoadThread.getLooper()); + } + + /** Restarts the loader thread */ + void start(Context context) { + mContext = context; + mCancelled = false; + if (!mStarted) { + // Start loading on the load thread + mStarted = true; + mLoadThreadHandler.post(this); + } else { + // Notify the load thread to start loading again + synchronized (mLoadThread) { + mLoadThread.notifyAll(); + } + } + } + + /** Requests the loader thread to stop after the current iteration */ + void stop() { + // Mark as cancelled for the thread to pick up + mCancelled = true; + // If we are waiting for the load queue for more tasks, then we can just reset the + // Context now, since nothing is using it + if (mWaitingOnLoadQueue) { + mContext = null; + } + } + + @Override + public void run() { + while (true) { + if (mCancelled) { + // We have to unset the context here, since the background thread may be using it + // when we call stop() + mContext = null; + // If we are cancelled, then wait until we are started again + synchronized(mLoadThread) { + try { + mLoadThread.wait(); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } else { + // If we've stopped the loader, then fall through to the above logic to wait on + // the load thread + processLoadQueueItem(); + + // If there are no other items in the list, then just wait until something is added + if (!mCancelled && mLoadQueue.isEmpty()) { + synchronized(mLoadQueue) { + try { + mWaitingOnLoadQueue = true; + mMainThreadHandler.post( + () -> mOnIdleChangedListener.onIdleChanged(true)); + mLoadQueue.wait(); + mMainThreadHandler.post( + () -> mOnIdleChangedListener.onIdleChanged(false)); + mWaitingOnLoadQueue = false; + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } + } + } + } + + /** + * This needs to be in a separate method to work around an surprising interpreter behavior: + * The register will keep the local reference to cachedThumbnailData even if it falls out of + * scope. Putting it into a method fixes this issue. + */ + private void processLoadQueueItem() { + // Load the next item from the queue + final Task t = mLoadQueue.nextTask(); + if (t != null) { + Drawable cachedIcon = mIconCache.get(t.key); + + // Load the icon if it is stale or we haven't cached one yet + if (cachedIcon == null) { + cachedIcon = ActivityManagerWrapper.getInstance().getBadgedTaskDescriptionIcon( + mContext, t.taskDescription, t.key.userId, mContext.getResources()); + + if (cachedIcon == null) { + ActivityInfo info = PackageManagerWrapper.getInstance().getActivityInfo( + t.key.getComponent(), t.key.userId); + if (info != null) { + if (DEBUG) Log.d(TAG, "Loading icon: " + t.key); + cachedIcon = ActivityManagerWrapper.getInstance().getBadgedActivityIcon( + info, t.key.userId); + } + } + + if (cachedIcon == null) { + cachedIcon = mDefaultIcon; + } + + // At this point, even if we can't load the icon, we will set the + // default icon. + mIconCache.put(t.key, cachedIcon); + } + + if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key); + final ThumbnailData thumbnailData = + ActivityManagerWrapper.getInstance().getTaskThumbnail(t.key.id, + true /* reducedResolution */); + + if (!mCancelled) { + // Notify that the task data has changed + final Drawable finalIcon = cachedIcon; + mMainThreadHandler.post( + () -> t.notifyTaskDataLoaded(thumbnailData, finalIcon)); + } + } + } + + interface OnIdleChangedListener { + void onIdleChanged(boolean idle); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/FilteredTaskList.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/FilteredTaskList.java new file mode 100644 index 000000000000..898d64a1ea1a --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/FilteredTaskList.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2017 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.shared.recents.model; + +import android.util.ArrayMap; +import android.util.SparseArray; + +import com.android.systemui.shared.recents.model.Task.TaskKey; + +import java.util.ArrayList; +import java.util.List; + +/** + * A list of filtered tasks. + */ +class FilteredTaskList { + + private final ArrayList<Task> mTasks = new ArrayList<>(); + private final ArrayList<Task> mFilteredTasks = new ArrayList<>(); + private final ArrayMap<TaskKey, Integer> mFilteredTaskIndices = new ArrayMap<>(); + private TaskFilter mFilter; + + /** Sets the task filter, and returns whether the set of filtered tasks have changed. */ + boolean setFilter(TaskFilter filter) { + ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks); + mFilter = filter; + updateFilteredTasks(); + return !prevFilteredTasks.equals(mFilteredTasks); + } + + /** Adds a new task to the task list */ + void add(Task t) { + mTasks.add(t); + updateFilteredTasks(); + } + + /** Sets the list of tasks */ + void set(List<Task> tasks) { + mTasks.clear(); + mTasks.addAll(tasks); + updateFilteredTasks(); + } + + /** Removes a task from the base list only if it is in the filtered list */ + boolean remove(Task t) { + if (mFilteredTasks.contains(t)) { + boolean removed = mTasks.remove(t); + updateFilteredTasks(); + return removed; + } + return false; + } + + /** Returns the index of this task in the list of filtered tasks */ + int indexOf(Task t) { + if (t != null && mFilteredTaskIndices.containsKey(t.key)) { + return mFilteredTaskIndices.get(t.key); + } + return -1; + } + + /** Returns the size of the list of filtered tasks */ + int size() { + return mFilteredTasks.size(); + } + + /** Returns whether the filtered list contains this task */ + boolean contains(Task t) { + return mFilteredTaskIndices.containsKey(t.key); + } + + /** Updates the list of filtered tasks whenever the base task list changes */ + private void updateFilteredTasks() { + mFilteredTasks.clear(); + if (mFilter != null) { + // Create a sparse array from task id to Task + SparseArray<Task> taskIdMap = new SparseArray<>(); + int taskCount = mTasks.size(); + for (int i = 0; i < taskCount; i++) { + Task t = mTasks.get(i); + taskIdMap.put(t.key.id, t); + } + + for (int i = 0; i < taskCount; i++) { + Task t = mTasks.get(i); + if (mFilter.acceptTask(taskIdMap, t, i)) { + mFilteredTasks.add(t); + } + } + } else { + mFilteredTasks.addAll(mTasks); + } + updateFilteredTaskIndices(); + } + + /** Updates the mapping of tasks to indices. */ + private void updateFilteredTaskIndices() { + int taskCount = mFilteredTasks.size(); + mFilteredTaskIndices.clear(); + for (int i = 0; i < taskCount; i++) { + Task t = mFilteredTasks.get(i); + mFilteredTaskIndices.put(t.key, i); + } + } + + /** Returns the list of filtered tasks */ + ArrayList<Task> getTasks() { + return mFilteredTasks; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java new file mode 100644 index 000000000000..24ba99840165 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2017 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.shared.recents.model; + +import static android.os.Process.setThreadPriority; + +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import android.util.ArraySet; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.shared.recents.model.Task.TaskCallbacks; +import com.android.systemui.shared.system.ActivityManagerWrapper; + +import java.util.ArrayDeque; +import java.util.ArrayList; + +/** + * Loader class that loads full-resolution thumbnails when appropriate. + */ +public class HighResThumbnailLoader implements TaskCallbacks { + + private final ActivityManagerWrapper mActivityManager; + + @GuardedBy("mLoadQueue") + private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>(); + @GuardedBy("mLoadQueue") + private final ArraySet<Task> mLoadingTasks = new ArraySet<>(); + @GuardedBy("mLoadQueue") + private boolean mLoaderIdling; + + private final ArrayList<Task> mVisibleTasks = new ArrayList<>(); + + private final Thread mLoadThread; + private final Handler mMainThreadHandler; + private final boolean mIsLowRamDevice; + private boolean mLoading; + private boolean mVisible; + private boolean mFlingingFast; + private boolean mTaskLoadQueueIdle; + + public HighResThumbnailLoader(ActivityManagerWrapper activityManager, Looper looper, + boolean isLowRamDevice) { + mActivityManager = activityManager; + mMainThreadHandler = new Handler(looper); + mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader"); + mLoadThread.start(); + mIsLowRamDevice = isLowRamDevice; + } + + public void setVisible(boolean visible) { + if (mIsLowRamDevice) { + return; + } + mVisible = visible; + updateLoading(); + } + + public void setFlingingFast(boolean flingingFast) { + if (mFlingingFast == flingingFast || mIsLowRamDevice) { + return; + } + mFlingingFast = flingingFast; + updateLoading(); + } + + /** + * Sets whether the other task load queue is idling. Avoid double-loading bitmaps by not + * starting this queue until the other queue is idling. + */ + public void setTaskLoadQueueIdle(boolean idle) { + if (mIsLowRamDevice) { + return; + } + mTaskLoadQueueIdle = idle; + updateLoading(); + } + + @VisibleForTesting + boolean isLoading() { + return mLoading; + } + + private void updateLoading() { + setLoading(mVisible && !mFlingingFast && mTaskLoadQueueIdle); + } + + private void setLoading(boolean loading) { + if (loading == mLoading) { + return; + } + synchronized (mLoadQueue) { + mLoading = loading; + if (!loading) { + stopLoading(); + } else { + startLoading(); + } + } + } + + @GuardedBy("mLoadQueue") + private void startLoading() { + for (int i = mVisibleTasks.size() - 1; i >= 0; i--) { + Task t = mVisibleTasks.get(i); + if ((t.thumbnail == null || t.thumbnail.reducedResolution) + && !mLoadQueue.contains(t) && !mLoadingTasks.contains(t)) { + mLoadQueue.add(t); + } + } + mLoadQueue.notifyAll(); + } + + @GuardedBy("mLoadQueue") + private void stopLoading() { + mLoadQueue.clear(); + mLoadQueue.notifyAll(); + } + + /** + * Needs to be called when a task becomes visible. Note that this is different from + * {@link TaskCallbacks#onTaskDataLoaded} as this method should only be called once when it + * becomes visible, whereas onTaskDataLoaded can be called multiple times whenever some data + * has been updated. + */ + public void onTaskVisible(Task t) { + t.addCallback(this); + mVisibleTasks.add(t); + if ((t.thumbnail == null || t.thumbnail.reducedResolution) && mLoading) { + synchronized (mLoadQueue) { + mLoadQueue.add(t); + mLoadQueue.notifyAll(); + } + } + } + + /** + * Needs to be called when a task becomes visible. See {@link #onTaskVisible} why this is + * different from {@link TaskCallbacks#onTaskDataUnloaded()} + */ + public void onTaskInvisible(Task t) { + t.removeCallback(this); + mVisibleTasks.remove(t); + synchronized (mLoadQueue) { + mLoadQueue.remove(t); + } + } + + @VisibleForTesting + void waitForLoaderIdle() { + while (true) { + synchronized (mLoadQueue) { + if (mLoadQueue.isEmpty() && mLoaderIdling) { + return; + } + } + SystemClock.sleep(100); + } + } + + @Override + public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) { + if (thumbnailData != null && !thumbnailData.reducedResolution) { + synchronized (mLoadQueue) { + mLoadQueue.remove(task); + } + } + } + + @Override + public void onTaskDataUnloaded() { + } + + @Override + public void onTaskWindowingModeChanged() { + } + + private final Runnable mLoader = new Runnable() { + + @Override + public void run() { + setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 1); + while (true) { + Task next = null; + synchronized (mLoadQueue) { + if (!mLoading || mLoadQueue.isEmpty()) { + try { + mLoaderIdling = true; + mLoadQueue.wait(); + mLoaderIdling = false; + } catch (InterruptedException e) { + // Don't care. + } + } else { + next = mLoadQueue.poll(); + if (next != null) { + mLoadingTasks.add(next); + } + } + } + if (next != null) { + loadTask(next); + } + } + } + + private void loadTask(Task t) { + ThumbnailData thumbnail = mActivityManager.getTaskThumbnail(t.key.id, + false /* reducedResolution */); + mMainThreadHandler.post(() -> { + synchronized (mLoadQueue) { + mLoadingTasks.remove(t); + } + if (mVisibleTasks.contains(t)) { + t.notifyTaskDataLoaded(thumbnail, t.icon); + } + }); + } + }; +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java new file mode 100644 index 000000000000..4834bb18f53c --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java @@ -0,0 +1,207 @@ +/* + * 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.shared.recents.model; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; + +import android.app.ActivityManager; +import android.app.KeyguardManager; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.SparseBooleanArray; + +import com.android.systemui.shared.recents.model.Task.TaskKey; +import com.android.systemui.shared.system.ActivityManagerWrapper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +/** + * This class stores the loading state as it goes through multiple stages of loading: + * 1) preloadRawTasks() will load the raw set of recents tasks from the system + * 2) preloadPlan() will construct a new task stack with all metadata and only icons and + * thumbnails that are currently in the cache + * 3) executePlan() will actually load and fill in the icons and thumbnails according to the load + * options specified, such that we can transition into the Recents activity seamlessly + */ +public class RecentsTaskLoadPlan { + + /** The set of conditions to preload tasks. */ + public static class PreloadOptions { + public boolean loadTitles = true; + } + + /** The set of conditions to load tasks. */ + public static class Options { + public int runningTaskId = -1; + public boolean loadIcons = true; + public boolean loadThumbnails = false; + public boolean onlyLoadForCache = false; + public boolean onlyLoadPausedActivities = false; + public int numVisibleTasks = 0; + public int numVisibleTaskThumbnails = 0; + } + + private final Context mContext; + private final KeyguardManager mKeyguardManager; + + private List<ActivityManager.RecentTaskInfo> mRawTasks; + private TaskStack mStack; + + private final SparseBooleanArray mTmpLockedUsers = new SparseBooleanArray(); + + public RecentsTaskLoadPlan(Context context) { + mContext = context; + mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + } + + /** + * Preloads the list of recent tasks from the system. After this call, the TaskStack will + * have a list of all the recent tasks with their metadata, not including icons or + * thumbnails which were not cached and have to be loaded. + * + * The tasks will be ordered by: + * - least-recent to most-recent stack tasks + * + * Note: Do not lock, since this can be calling back to the loader, which separately also drives + * this call (callers should synchronize on the loader before making this call). + */ + public void preloadPlan(PreloadOptions opts, RecentsTaskLoader loader, int runningTaskId, + int currentUserId) { + Resources res = mContext.getResources(); + ArrayList<Task> allTasks = new ArrayList<>(); + if (mRawTasks == null) { + mRawTasks = ActivityManagerWrapper.getInstance().getRecentTasks( + ActivityManager.getMaxRecentTasksStatic(), currentUserId); + + // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it + Collections.reverse(mRawTasks); + } + + int taskCount = mRawTasks.size(); + for (int i = 0; i < taskCount; i++) { + ActivityManager.RecentTaskInfo t = mRawTasks.get(i); + + // Compose the task key + final int windowingMode = t.configuration.windowConfiguration.getWindowingMode(); + TaskKey taskKey = new TaskKey(t.persistentId, windowingMode, t.baseIntent, + t.userId, t.lastActiveTime); + + boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM; + boolean isStackTask = !isFreeformTask; + boolean isLaunchTarget = taskKey.id == runningTaskId; + + ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey); + if (info == null) { + continue; + } + + // Load the title, icon, and color + String title = opts.loadTitles + ? loader.getAndUpdateActivityTitle(taskKey, t.taskDescription) + : ""; + String titleDescription = opts.loadTitles + ? loader.getAndUpdateContentDescription(taskKey, t.taskDescription) + : ""; + Drawable icon = isStackTask + ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false) + : null; + ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey, + false /* loadIfNotCached */, false /* storeInCache */); + int activityColor = loader.getActivityPrimaryColor(t.taskDescription); + int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription); + boolean isSystemApp = (info != null) && + ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); + + // TODO: Refactor to not do this every preload + if (mTmpLockedUsers.indexOfKey(t.userId) < 0) { + mTmpLockedUsers.put(t.userId, mKeyguardManager.isDeviceLocked(t.userId)); + } + boolean isLocked = mTmpLockedUsers.get(t.userId); + + // Add the task to the stack + Task task = new Task(taskKey, icon, + thumbnail, title, titleDescription, activityColor, backgroundColor, + isLaunchTarget, isStackTask, isSystemApp, t.supportsSplitScreenMultiWindow, + t.taskDescription, t.resizeMode, t.topActivity, isLocked); + + allTasks.add(task); + } + + // Initialize the stacks + mStack = new TaskStack(); + mStack.setTasks(allTasks, false /* notifyStackChanges */); + } + + /** + * Called to apply the actual loading based on the specified conditions. + * + * Note: Do not lock, since this can be calling back to the loader, which separately also drives + * this call (callers should synchronize on the loader before making this call). + */ + public void executePlan(Options opts, RecentsTaskLoader loader) { + Resources res = mContext.getResources(); + + // Iterate through each of the tasks and load them according to the load conditions. + ArrayList<Task> tasks = mStack.getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + TaskKey taskKey = task.key; + + boolean isRunningTask = (task.key.id == opts.runningTaskId); + boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks); + boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails); + + // If requested, skip the running task + if (opts.onlyLoadPausedActivities && isRunningTask) { + continue; + } + + if (opts.loadIcons && (isRunningTask || isVisibleTask)) { + if (task.icon == null) { + task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, res, + true); + } + } + if (opts.loadThumbnails && isVisibleThumbnail) { + task.thumbnail = loader.getAndUpdateThumbnail(taskKey, + true /* loadIfNotCached */, true /* storeInCache */); + } + } + } + + /** + * Returns the TaskStack from the preloaded list of recent tasks. + */ + public TaskStack getTaskStack() { + return mStack; + } + + /** Returns whether there are any tasks in any stacks. */ + public boolean hasTasks() { + if (mStack != null) { + return mStack.getTaskCount() > 0; + } + return false; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoader.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoader.java new file mode 100644 index 000000000000..0f68026c7464 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoader.java @@ -0,0 +1,460 @@ +/* + * 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.shared.recents.model; + +import android.app.ActivityManager; +import android.content.ComponentCallbacks2; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Looper; +import android.os.Trace; +import android.util.Log; +import android.util.LruCache; + +import com.android.internal.annotations.GuardedBy; +import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.Options; +import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions; +import com.android.systemui.shared.recents.model.Task.TaskKey; +import com.android.systemui.shared.recents.model.TaskKeyLruCache.EvictionCallback; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.PackageManagerWrapper; + +import java.io.PrintWriter; +import java.util.Map; + + +/** + * Recents task loader + */ +public class RecentsTaskLoader { + private static final String TAG = "RecentsTaskLoader"; + private static final boolean DEBUG = false; + + /** Levels of svelte in increasing severity/austerity. */ + // No svelting. + public static final int SVELTE_NONE = 0; + // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable + // caching thumbnails as you scroll. + public static final int SVELTE_LIMIT_CACHE = 1; + // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and + // evict all thumbnails when hidden. + public static final int SVELTE_DISABLE_CACHE = 2; + // Disable all thumbnail loading. + public static final int SVELTE_DISABLE_LOADING = 3; + + private final Context mContext; + + // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos + // for many tasks, which we use to get the activity labels and icons. Unlike the other caches + // below, this is per-package so we can't invalidate the items in the cache based on the last + // active time. Instead, we rely on the PackageMonitor to keep us informed whenever a + // package in the cache has been updated, so that we may remove it. + private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache; + private final TaskKeyLruCache<Drawable> mIconCache; + private final TaskKeyLruCache<String> mActivityLabelCache; + private final TaskKeyLruCache<String> mContentDescriptionCache; + private final TaskResourceLoadQueue mLoadQueue; + private final BackgroundTaskLoader mLoader; + private final HighResThumbnailLoader mHighResThumbnailLoader; + @GuardedBy("this") + private final TaskKeyStrongCache<ThumbnailData> mThumbnailCache = new TaskKeyStrongCache<>(); + @GuardedBy("this") + private final TaskKeyStrongCache<ThumbnailData> mTempCache = new TaskKeyStrongCache<>(); + private final int mMaxThumbnailCacheSize; + private final int mMaxIconCacheSize; + private int mNumVisibleTasksLoaded; + private int mSvelteLevel; + + private int mDefaultTaskBarBackgroundColor; + private int mDefaultTaskViewBackgroundColor; + private final BitmapDrawable mDefaultIcon; + + private EvictionCallback mClearActivityInfoOnEviction = new EvictionCallback() { + @Override + public void onEntryEvicted(TaskKey key) { + if (key != null) { + mActivityInfoCache.remove(key.getComponent()); + } + } + }; + + public RecentsTaskLoader(Context context, int maxThumbnailCacheSize, int maxIconCacheSize, + int svelteLevel) { + mContext = context; + mMaxThumbnailCacheSize = maxThumbnailCacheSize; + mMaxIconCacheSize = maxIconCacheSize; + mSvelteLevel = svelteLevel; + + // Create the default assets + Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); + icon.eraseColor(0); + mDefaultIcon = new BitmapDrawable(context.getResources(), icon); + + // Initialize the proxy, cache and loaders + int numRecentTasks = ActivityManager.getMaxRecentTasksStatic(); + mHighResThumbnailLoader = new HighResThumbnailLoader(ActivityManagerWrapper.getInstance(), + Looper.getMainLooper(), ActivityManager.isLowRamDeviceStatic()); + mLoadQueue = new TaskResourceLoadQueue(); + mIconCache = new TaskKeyLruCache<>(mMaxIconCacheSize, mClearActivityInfoOnEviction); + mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction); + mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks, + mClearActivityInfoOnEviction); + mActivityInfoCache = new LruCache<>(numRecentTasks); + mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultIcon, + mHighResThumbnailLoader::setTaskLoadQueueIdle); + } + + /** + * Sets the default task bar/view colors if none are provided by the app. + */ + public void setDefaultColors(int defaultTaskBarBackgroundColor, + int defaultTaskViewBackgroundColor) { + mDefaultTaskBarBackgroundColor = defaultTaskBarBackgroundColor; + mDefaultTaskViewBackgroundColor = defaultTaskViewBackgroundColor; + } + + /** Returns the size of the app icon cache. */ + public int getIconCacheSize() { + return mMaxIconCacheSize; + } + + /** Returns the size of the thumbnail cache. */ + public int getThumbnailCacheSize() { + return mMaxThumbnailCacheSize; + } + + public HighResThumbnailLoader getHighResThumbnailLoader() { + return mHighResThumbnailLoader; + } + + /** Preloads recents tasks using the specified plan to store the output. */ + public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId) { + preloadTasks(plan, runningTaskId, ActivityManagerWrapper.getInstance().getCurrentUserId()); + } + + /** Preloads recents tasks using the specified plan to store the output. */ + public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId, + int currentUserId) { + try { + Trace.beginSection("preloadPlan"); + plan.preloadPlan(new PreloadOptions(), this, runningTaskId, currentUserId); + } finally { + Trace.endSection(); + } + } + + /** Begins loading the heavy task data according to the specified options. */ + public synchronized void loadTasks(RecentsTaskLoadPlan plan, Options opts) { + if (opts == null) { + throw new RuntimeException("Requires load options"); + } + if (opts.onlyLoadForCache && opts.loadThumbnails) { + // If we are loading for the cache, we'd like to have the real cache only include the + // visible thumbnails. However, we also don't want to reload already cached thumbnails. + // Thus, we copy over the current entries into a second cache, and clear the real cache, + // such that the real cache only contains visible thumbnails. + mTempCache.copyEntries(mThumbnailCache); + mThumbnailCache.evictAll(); + } + plan.executePlan(opts, this); + mTempCache.evictAll(); + if (!opts.onlyLoadForCache) { + mNumVisibleTasksLoaded = opts.numVisibleTasks; + } + } + + /** + * Acquires the task resource data directly from the cache, loading if necessary. + */ + public void loadTaskData(Task t) { + Drawable icon = mIconCache.getAndInvalidateIfModified(t.key); + icon = icon != null ? icon : mDefaultIcon; + mLoadQueue.addTask(t); + t.notifyTaskDataLoaded(t.thumbnail, icon); + } + + /** Releases the task resource data back into the pool. */ + public void unloadTaskData(Task t) { + mLoadQueue.removeTask(t); + t.notifyTaskDataUnloaded(mDefaultIcon); + } + + /** Completely removes the resource data from the pool. */ + public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) { + mLoadQueue.removeTask(t); + mIconCache.remove(t.key); + mActivityLabelCache.remove(t.key); + mContentDescriptionCache.remove(t.key); + if (notifyTaskDataUnloaded) { + t.notifyTaskDataUnloaded(mDefaultIcon); + } + } + + /** + * Handles signals from the system, trimming memory when requested to prevent us from running + * out of memory. + */ + public synchronized void onTrimMemory(int level) { + switch (level) { + case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: + // Stop the loader immediately when the UI is no longer visible + stopLoader(); + mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded, + mMaxIconCacheSize / 2)); + break; + case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: + case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: + // We are leaving recents, so trim the data a bit + mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2)); + mActivityInfoCache.trimToSize(Math.max(1, + ActivityManager.getMaxRecentTasksStatic() / 2)); + break; + case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: + case ComponentCallbacks2.TRIM_MEMORY_MODERATE: + // We are going to be low on memory + mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4)); + mActivityInfoCache.trimToSize(Math.max(1, + ActivityManager.getMaxRecentTasksStatic() / 4)); + break; + case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: + case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: + // We are low on memory, so release everything + mIconCache.evictAll(); + mActivityInfoCache.evictAll(); + // The cache is small, only clear the label cache when we are critical + mActivityLabelCache.evictAll(); + mContentDescriptionCache.evictAll(); + mThumbnailCache.evictAll(); + break; + default: + break; + } + } + + public void onPackageChanged(String packageName) { + // Remove all the cached activity infos for this package. The other caches do not need to + // be pruned at this time, as the TaskKey expiration checks will flush them next time their + // cached contents are requested + Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot(); + for (ComponentName cn : activityInfoCache.keySet()) { + if (cn.getPackageName().equals(packageName)) { + if (DEBUG) { + Log.d(TAG, "Removing activity info from cache: " + cn); + } + mActivityInfoCache.remove(cn); + } + } + } + + /** + * Returns the cached task label if the task key is not expired, updating the cache if it is. + */ + String getAndUpdateActivityTitle(TaskKey taskKey, ActivityManager.TaskDescription td) { + // Return the task description label if it exists + if (td != null && td.getLabel() != null) { + return td.getLabel(); + } + // Return the cached activity label if it exists + String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey); + if (label != null) { + return label; + } + // All short paths failed, load the label from the activity info and cache it + ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); + if (activityInfo != null) { + label = ActivityManagerWrapper.getInstance().getBadgedActivityLabel(activityInfo, + taskKey.userId); + mActivityLabelCache.put(taskKey, label); + return label; + } + // If the activity info does not exist or fails to load, return an empty label for now, + // but do not cache it + return ""; + } + + /** + * Returns the cached task content description if the task key is not expired, updating the + * cache if it is. + */ + String getAndUpdateContentDescription(TaskKey taskKey, ActivityManager.TaskDescription td) { + // Return the cached content description if it exists + String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey); + if (label != null) { + return label; + } + + // All short paths failed, load the label from the activity info and cache it + ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); + if (activityInfo != null) { + label = ActivityManagerWrapper.getInstance().getBadgedContentDescription( + activityInfo, taskKey.userId, td); + if (td == null) { + // Only add to the cache if the task description is null, otherwise, it is possible + // for the task description to change between calls without the last active time + // changing (ie. between preloading and Overview starting) which would lead to stale + // content descriptions + // TODO: Investigate improving this + mContentDescriptionCache.put(taskKey, label); + } + return label; + } + // If the content description does not exist, return an empty label for now, but do not + // cache it + return ""; + } + + /** + * Returns the cached task icon if the task key is not expired, updating the cache if it is. + */ + Drawable getAndUpdateActivityIcon(TaskKey taskKey, ActivityManager.TaskDescription td, + Resources res, boolean loadIfNotCached) { + // Return the cached activity icon if it exists + Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey); + if (icon != null) { + return icon; + } + + if (loadIfNotCached) { + // Return and cache the task description icon if it exists + icon = ActivityManagerWrapper.getInstance().getBadgedTaskDescriptionIcon(mContext, td, + taskKey.userId, res); + if (icon != null) { + mIconCache.put(taskKey, icon); + return icon; + } + + // Load the icon from the activity info and cache it + ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); + if (activityInfo != null) { + icon = ActivityManagerWrapper.getInstance().getBadgedActivityIcon(activityInfo, + taskKey.userId); + if (icon != null) { + mIconCache.put(taskKey, icon); + return icon; + } + } + } + // We couldn't load any icon + return null; + } + + /** + * Returns the cached thumbnail if the task key is not expired, updating the cache if it is. + */ + synchronized ThumbnailData getAndUpdateThumbnail(TaskKey taskKey, boolean loadIfNotCached, + boolean storeInCache) { + ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey); + if (cached != null) { + return cached; + } + + cached = mTempCache.getAndInvalidateIfModified(taskKey); + if (cached != null) { + mThumbnailCache.put(taskKey, cached); + return cached; + } + + if (loadIfNotCached) { + if (mSvelteLevel < SVELTE_DISABLE_LOADING) { + // Load the thumbnail from the system + ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance().getTaskThumbnail( + taskKey.id, true /* reducedResolution */); + if (thumbnailData.thumbnail != null) { + if (storeInCache) { + mThumbnailCache.put(taskKey, thumbnailData); + } + return thumbnailData; + } + } + } + + // We couldn't load any thumbnail + return null; + } + + /** + * Returns the task's primary color if possible, defaulting to the default color if there is + * no specified primary color. + */ + int getActivityPrimaryColor(ActivityManager.TaskDescription td) { + if (td != null && td.getPrimaryColor() != 0) { + return td.getPrimaryColor(); + } + return mDefaultTaskBarBackgroundColor; + } + + /** + * Returns the task's background color if possible. + */ + int getActivityBackgroundColor(ActivityManager.TaskDescription td) { + if (td != null && td.getBackgroundColor() != 0) { + return td.getBackgroundColor(); + } + return mDefaultTaskViewBackgroundColor; + } + + /** + * Returns the activity info for the given task key, retrieving one from the system if the + * task key is expired. + */ + ActivityInfo getAndUpdateActivityInfo(TaskKey taskKey) { + ComponentName cn = taskKey.getComponent(); + ActivityInfo activityInfo = mActivityInfoCache.get(cn); + if (activityInfo == null) { + activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(cn, taskKey.userId); + if (cn == null || activityInfo == null) { + Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " + + activityInfo); + return null; + } + mActivityInfoCache.put(cn, activityInfo); + } + return activityInfo; + } + + /** + * Starts loading tasks. + */ + public void startLoader(Context ctx) { + mLoader.start(ctx); + } + + /** + * Stops the task loader and clears all queued, pending task loads. + */ + private void stopLoader() { + mLoader.stop(); + mLoadQueue.clearTasks(); + } + + public synchronized void dump(String prefix, PrintWriter writer) { + String innerPrefix = prefix + " "; + + writer.print(prefix); writer.println(TAG); + writer.print(prefix); writer.println("Icon Cache"); + mIconCache.dump(innerPrefix, writer); + writer.print(prefix); writer.println("Thumbnail Cache"); + mThumbnailCache.dump(innerPrefix, writer); + writer.print(prefix); writer.println("Temp Thumbnail Cache"); + mTempCache.dump(innerPrefix, writer); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java new file mode 100644 index 000000000000..6bddbe01b11b --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -0,0 +1,295 @@ +/* + * 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.shared.recents.model; + +import android.app.ActivityManager.TaskDescription; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.view.ViewDebug; + +import com.android.systemui.shared.recents.utilities.Utilities; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Objects; + +/** + * A task represents the top most task in the system's task stack. + */ +public class Task { + + public static final String TAG = "Task"; + + /* Task callbacks */ + public interface TaskCallbacks { + /* Notifies when a task has been bound */ + void onTaskDataLoaded(Task task, ThumbnailData thumbnailData); + /* Notifies when a task has been unbound */ + void onTaskDataUnloaded(); + /* Notifies when a task's windowing mode has changed. */ + void onTaskWindowingModeChanged(); + } + + /* The Task Key represents the unique primary key for the task */ + public static class TaskKey { + @ViewDebug.ExportedProperty(category="recents") + public final int id; + @ViewDebug.ExportedProperty(category="recents") + public int windowingMode; + @ViewDebug.ExportedProperty(category="recents") + public final Intent baseIntent; + @ViewDebug.ExportedProperty(category="recents") + public final int userId; + @ViewDebug.ExportedProperty(category="recents") + public long lastActiveTime; + + private int mHashCode; + + public TaskKey(int id, int windowingMode, Intent intent, int userId, long lastActiveTime) { + this.id = id; + this.windowingMode = windowingMode; + this.baseIntent = intent; + this.userId = userId; + this.lastActiveTime = lastActiveTime; + updateHashCode(); + } + + public void setWindowingMode(int windowingMode) { + this.windowingMode = windowingMode; + updateHashCode(); + } + + public ComponentName getComponent() { + return this.baseIntent.getComponent(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof TaskKey)) { + return false; + } + TaskKey otherKey = (TaskKey) o; + return id == otherKey.id + && windowingMode == otherKey.windowingMode + && userId == otherKey.userId; + } + + @Override + public int hashCode() { + return mHashCode; + } + + @Override + public String toString() { + return "id=" + id + " windowingMode=" + windowingMode + " user=" + userId + + " lastActiveTime=" + lastActiveTime; + } + + private void updateHashCode() { + mHashCode = Objects.hash(id, windowingMode, userId); + } + } + + @ViewDebug.ExportedProperty(deepExport=true, prefix="key_") + public TaskKey key; + + /** + * The temporary sort index in the stack, used when ordering the stack. + */ + public int temporarySortIndexInStack; + + /** + * The icon is the task description icon (if provided), which falls back to the activity icon, + * which can then fall back to the application icon. + */ + public Drawable icon; + public ThumbnailData thumbnail; + @ViewDebug.ExportedProperty(category="recents") + public String title; + @ViewDebug.ExportedProperty(category="recents") + public String titleDescription; + @ViewDebug.ExportedProperty(category="recents") + public int colorPrimary; + @ViewDebug.ExportedProperty(category="recents") + public int colorBackground; + @ViewDebug.ExportedProperty(category="recents") + public boolean useLightOnPrimaryColor; + + /** + * The task description for this task, only used to reload task icons. + */ + public TaskDescription taskDescription; + + /** + * The state isLaunchTarget will be set for the correct task upon launching Recents. + */ + @ViewDebug.ExportedProperty(category="recents") + public boolean isLaunchTarget; + @ViewDebug.ExportedProperty(category="recents") + public boolean isStackTask; + @ViewDebug.ExportedProperty(category="recents") + public boolean isSystemApp; + @ViewDebug.ExportedProperty(category="recents") + public boolean isDockable; + + /** + * Resize mode. See {@link ActivityInfo#resizeMode}. + */ + @ViewDebug.ExportedProperty(category="recents") + public int resizeMode; + + @ViewDebug.ExportedProperty(category="recents") + public ComponentName topActivity; + + @ViewDebug.ExportedProperty(category="recents") + public boolean isLocked; + + private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>(); + + public Task() { + // Do nothing + } + + public Task(TaskKey key, Drawable icon, ThumbnailData thumbnail, String title, + String titleDescription, int colorPrimary, int colorBackground, boolean isLaunchTarget, + boolean isStackTask, boolean isSystemApp, boolean isDockable, + TaskDescription taskDescription, int resizeMode, ComponentName topActivity, + boolean isLocked) { + this.key = key; + this.icon = icon; + this.thumbnail = thumbnail; + this.title = title; + this.titleDescription = titleDescription; + this.colorPrimary = colorPrimary; + this.colorBackground = colorBackground; + this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary, + Color.WHITE) > 3f; + this.taskDescription = taskDescription; + this.isLaunchTarget = isLaunchTarget; + this.isStackTask = isStackTask; + this.isSystemApp = isSystemApp; + this.isDockable = isDockable; + this.resizeMode = resizeMode; + this.topActivity = topActivity; + this.isLocked = isLocked; + } + + /** + * Copies the metadata from another task, but retains the current callbacks. + */ + public void copyFrom(Task o) { + this.key = o.key; + this.icon = o.icon; + this.thumbnail = o.thumbnail; + this.title = o.title; + this.titleDescription = o.titleDescription; + this.colorPrimary = o.colorPrimary; + this.colorBackground = o.colorBackground; + this.useLightOnPrimaryColor = o.useLightOnPrimaryColor; + this.taskDescription = o.taskDescription; + this.isLaunchTarget = o.isLaunchTarget; + this.isStackTask = o.isStackTask; + this.isSystemApp = o.isSystemApp; + this.isDockable = o.isDockable; + this.resizeMode = o.resizeMode; + this.isLocked = o.isLocked; + this.topActivity = o.topActivity; + } + + /** + * Add a callback. + */ + public void addCallback(TaskCallbacks cb) { + if (!mCallbacks.contains(cb)) { + mCallbacks.add(cb); + } + } + + /** + * Remove a callback. + */ + public void removeCallback(TaskCallbacks cb) { + mCallbacks.remove(cb); + } + + /** Updates the task's windowing mode. */ + public void setWindowingMode(int windowingMode) { + key.setWindowingMode(windowingMode); + int callbackCount = mCallbacks.size(); + for (int i = 0; i < callbackCount; i++) { + mCallbacks.get(i).onTaskWindowingModeChanged(); + } + } + + /** Notifies the callback listeners that this task has been loaded */ + public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) { + this.icon = applicationIcon; + this.thumbnail = thumbnailData; + int callbackCount = mCallbacks.size(); + for (int i = 0; i < callbackCount; i++) { + mCallbacks.get(i).onTaskDataLoaded(this, thumbnailData); + } + } + + /** Notifies the callback listeners that this task has been unloaded */ + public void notifyTaskDataUnloaded(Drawable defaultApplicationIcon) { + icon = defaultApplicationIcon; + thumbnail = null; + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + mCallbacks.get(i).onTaskDataUnloaded(); + } + } + + /** + * Returns the top activity component. + */ + public ComponentName getTopComponent() { + return topActivity != null + ? topActivity + : key.baseIntent.getComponent(); + } + + @Override + public boolean equals(Object o) { + // Check that the id matches + Task t = (Task) o; + return key.equals(t.key); + } + + @Override + public String toString() { + return "[" + key.toString() + "] " + title; + } + + public void dump(String prefix, PrintWriter writer) { + writer.print(prefix); writer.print(key); + if (!isDockable) { + writer.print(" dockable=N"); + } + if (isLaunchTarget) { + writer.print(" launchTarget=Y"); + } + if (isLocked) { + writer.print(" locked=Y"); + } + writer.print(" "); writer.print(title); + writer.println(); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskFilter.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskFilter.java new file mode 100644 index 000000000000..5f3dcd16e074 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskFilter.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 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.shared.recents.model; + +import android.util.SparseArray; + +/** + * An interface for a task filter to query whether a particular task should show in a stack. + */ +public interface TaskFilter { + /** Returns whether the filter accepts the specified task */ + boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index); +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyCache.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyCache.java new file mode 100644 index 000000000000..4bf3500a3405 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyCache.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 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.shared.recents.model; + +import android.util.Log; +import android.util.SparseArray; + +import com.android.systemui.shared.recents.model.Task.TaskKey; + +/** + * Base class for both strong and LRU task key cache. + */ +public abstract class TaskKeyCache<V> { + + protected static final String TAG = "TaskKeyCache"; + + protected final SparseArray<TaskKey> mKeys = new SparseArray<>(); + + /** + * Gets a specific entry in the cache with the specified key, regardless of whether the cached + * value is valid or not. + */ + final V get(TaskKey key) { + return getCacheEntry(key.id); + } + + /** + * Returns the value only if the key is valid (has not been updated since the last time it was + * in the cache) + */ + final V getAndInvalidateIfModified(TaskKey key) { + TaskKey lastKey = mKeys.get(key.id); + if (lastKey != null) { + if ((lastKey.windowingMode != key.windowingMode) || + (lastKey.lastActiveTime != key.lastActiveTime)) { + // The task has updated (been made active since the last time it was put into the + // LRU cache) or the stack id for the task has changed, invalidate that cache item + remove(key); + return null; + } + } + // Either the task does not exist in the cache, or the last active time is the same as + // the key specified, so return what is in the cache + return getCacheEntry(key.id); + } + + /** Puts an entry in the cache for a specific key. */ + final void put(TaskKey key, V value) { + if (key == null || value == null) { + Log.e(TAG, "Unexpected null key or value: " + key + ", " + value); + return; + } + mKeys.put(key.id, key); + putCacheEntry(key.id, value); + } + + + /** Removes a cache entry for a specific key. */ + final void remove(TaskKey key) { + // Remove the key after the cache value because we need it to make the callback + removeCacheEntry(key.id); + mKeys.remove(key.id); + } + + /** Removes all the entries in the cache. */ + final void evictAll() { + evictAllCache(); + mKeys.clear(); + } + + protected abstract V getCacheEntry(int id); + protected abstract void putCacheEntry(int id, V value); + protected abstract void removeCacheEntry(int id); + protected abstract void evictAllCache(); +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyLruCache.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyLruCache.java new file mode 100644 index 000000000000..0ba2c3bf6e3c --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyLruCache.java @@ -0,0 +1,96 @@ +/* + * 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.shared.recents.model; + +import android.util.LruCache; + +import com.android.systemui.shared.recents.model.Task.TaskKey; + +import java.io.PrintWriter; + +/** + * A mapping of {@link TaskKey} to value, with additional LRU functionality where the least + * recently referenced key/values will be evicted as more values than the given cache size are + * inserted. + * + * In addition, this also allows the caller to invalidate cached values for keys that have since + * changed. + */ +public class TaskKeyLruCache<V> extends TaskKeyCache<V> { + + public interface EvictionCallback { + void onEntryEvicted(TaskKey key); + } + + private final LruCache<Integer, V> mCache; + private final EvictionCallback mEvictionCallback; + + public TaskKeyLruCache(int cacheSize) { + this(cacheSize, null); + } + + public TaskKeyLruCache(int cacheSize, EvictionCallback evictionCallback) { + mEvictionCallback = evictionCallback; + mCache = new LruCache<Integer, V>(cacheSize) { + + @Override + protected void entryRemoved(boolean evicted, Integer taskId, V oldV, V newV) { + if (mEvictionCallback != null) { + mEvictionCallback.onEntryEvicted(mKeys.get(taskId)); + } + mKeys.remove(taskId); + } + }; + } + + /** Trims the cache to a specific size */ + final void trimToSize(int cacheSize) { + mCache.trimToSize(cacheSize); + } + + public void dump(String prefix, PrintWriter writer) { + String innerPrefix = prefix + " "; + + writer.print(prefix); writer.print(TAG); + writer.print(" numEntries="); writer.print(mKeys.size()); + writer.println(); + int keyCount = mKeys.size(); + for (int i = 0; i < keyCount; i++) { + writer.print(innerPrefix); writer.println(mKeys.get(mKeys.keyAt(i))); + } + } + + @Override + protected V getCacheEntry(int id) { + return mCache.get(id); + } + + @Override + protected void putCacheEntry(int id, V value) { + mCache.put(id, value); + } + + @Override + protected void removeCacheEntry(int id) { + mCache.remove(id); + } + + @Override + protected void evictAllCache() { + mCache.evictAll(); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java new file mode 100644 index 000000000000..4408eced3e93 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017 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.shared.recents.model; + +import android.util.ArrayMap; + +import com.android.systemui.shared.recents.model.Task.TaskKey; + +import java.io.PrintWriter; + +/** + * Like {@link TaskKeyLruCache}, but without LRU functionality. + */ +public class TaskKeyStrongCache<V> extends TaskKeyCache<V> { + + private static final String TAG = "TaskKeyCache"; + + private final ArrayMap<Integer, V> mCache = new ArrayMap<>(); + + final void copyEntries(TaskKeyStrongCache<V> other) { + for (int i = other.mKeys.size() - 1; i >= 0; i--) { + TaskKey key = other.mKeys.valueAt(i); + put(key, other.mCache.get(key.id)); + } + } + + public void dump(String prefix, PrintWriter writer) { + String innerPrefix = prefix + " "; + writer.print(prefix); writer.print(TAG); + writer.print(" numEntries="); writer.print(mKeys.size()); + writer.println(); + int keyCount = mKeys.size(); + for (int i = 0; i < keyCount; i++) { + writer.print(innerPrefix); writer.println(mKeys.get(mKeys.keyAt(i))); + } + } + + @Override + protected V getCacheEntry(int id) { + return mCache.get(id); + } + + @Override + protected void putCacheEntry(int id, V value) { + mCache.put(id, value); + } + + @Override + protected void removeCacheEntry(int id) { + mCache.remove(id); + } + + @Override + protected void evictAllCache() { + mCache.clear(); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java new file mode 100644 index 000000000000..fbb6acebc8e0 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 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.shared.recents.model; + +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * A Task load queue + */ +class TaskResourceLoadQueue { + + private final ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<>(); + + /** Adds a new task to the load queue */ + void addTask(Task t) { + if (!mQueue.contains(t)) { + mQueue.add(t); + } + synchronized(this) { + notifyAll(); + } + } + + /** + * Retrieves the next task from the load queue, as well as whether we want that task to be + * force reloaded. + */ + Task nextTask() { + return mQueue.poll(); + } + + /** Removes a task from the load queue */ + void removeTask(Task t) { + mQueue.remove(t); + } + + /** Clears all the tasks from the load queue */ + void clearTasks() { + mQueue.clear(); + } + + /** Returns whether the load queue is empty */ + boolean isEmpty() { + return mQueue.isEmpty(); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskStack.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskStack.java new file mode 100644 index 000000000000..a36939769477 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskStack.java @@ -0,0 +1,395 @@ +/* + * 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.shared.recents.model; + +import android.content.ComponentName; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.systemui.shared.recents.model.Task.TaskKey; +import com.android.systemui.shared.recents.utilities.AnimationProps; +import com.android.systemui.shared.system.PackageManagerWrapper; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + + +/** + * The task stack contains a list of multiple tasks. + */ +public class TaskStack { + + private static final String TAG = "TaskStack"; + + /** Task stack callbacks */ + public interface TaskStackCallbacks { + /** + * Notifies when a new task has been added to the stack. + */ + void onStackTaskAdded(TaskStack stack, Task newTask); + + /** + * Notifies when a task has been removed from the stack. + */ + void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask, + AnimationProps animation, boolean fromDockGesture, + boolean dismissRecentsIfAllRemoved); + + /** + * Notifies when all tasks have been removed from the stack. + */ + void onStackTasksRemoved(TaskStack stack); + + /** + * Notifies when tasks in the stack have been updated. + */ + void onStackTasksUpdated(TaskStack stack); + } + + private final ArrayList<Task> mRawTaskList = new ArrayList<>(); + private final FilteredTaskList mStackTaskList = new FilteredTaskList(); + private TaskStackCallbacks mCb; + + public TaskStack() { + // Ensure that we only show stack tasks + mStackTaskList.setFilter((taskIdMap, t, index) -> t.isStackTask); + } + + /** Sets the callbacks for this task stack. */ + public void setCallbacks(TaskStackCallbacks cb) { + mCb = cb; + } + + /** + * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on + * how they should update themselves. + */ + public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) { + removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */); + } + + /** + * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on + * how they should update themselves. + */ + public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture, + boolean dismissRecentsIfAllRemoved) { + if (mStackTaskList.contains(t)) { + mStackTaskList.remove(t); + Task newFrontMostTask = getFrontMostTask(); + if (mCb != null) { + // Notify that a task has been removed + mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation, + fromDockGesture, dismissRecentsIfAllRemoved); + } + } + mRawTaskList.remove(t); + } + + /** + * Removes all tasks from the stack. + */ + public void removeAllTasks(boolean notifyStackChanges) { + ArrayList<Task> tasks = mStackTaskList.getTasks(); + for (int i = tasks.size() - 1; i >= 0; i--) { + Task t = tasks.get(i); + mStackTaskList.remove(t); + mRawTaskList.remove(t); + } + if (mCb != null && notifyStackChanges) { + // Notify that all tasks have been removed + mCb.onStackTasksRemoved(this); + } + } + + + /** + * @see #setTasks(List, boolean) + */ + public void setTasks(TaskStack stack, boolean notifyStackChanges) { + setTasks(stack.mRawTaskList, notifyStackChanges); + } + + /** + * Sets a few tasks in one go, without calling any callbacks. + * + * @param tasks the new set of tasks to replace the current set. + * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks. + */ + public void setTasks(List<Task> tasks, boolean notifyStackChanges) { + // Compute a has set for each of the tasks + ArrayMap<TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList); + ArrayMap<TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks); + ArrayList<Task> addedTasks = new ArrayList<>(); + ArrayList<Task> removedTasks = new ArrayList<>(); + ArrayList<Task> allTasks = new ArrayList<>(); + + // Disable notifications if there are no callbacks + if (mCb == null) { + notifyStackChanges = false; + } + + // Remove any tasks that no longer exist + int taskCount = mRawTaskList.size(); + for (int i = taskCount - 1; i >= 0; i--) { + Task task = mRawTaskList.get(i); + if (!newTasksMap.containsKey(task.key)) { + if (notifyStackChanges) { + removedTasks.add(task); + } + } + } + + // Add any new tasks + taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task newTask = tasks.get(i); + Task currentTask = currentTasksMap.get(newTask.key); + if (currentTask == null && notifyStackChanges) { + addedTasks.add(newTask); + } else if (currentTask != null) { + // The current task has bound callbacks, so just copy the data from the new task + // state and add it back into the list + currentTask.copyFrom(newTask); + newTask = currentTask; + } + allTasks.add(newTask); + } + + // Sort all the tasks to ensure they are ordered correctly + for (int i = allTasks.size() - 1; i >= 0; i--) { + allTasks.get(i).temporarySortIndexInStack = i; + } + + mStackTaskList.set(allTasks); + mRawTaskList.clear(); + mRawTaskList.addAll(allTasks); + + // Only callback for the removed tasks after the stack has updated + int removedTaskCount = removedTasks.size(); + Task newFrontMostTask = getFrontMostTask(); + for (int i = 0; i < removedTaskCount; i++) { + mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask, + AnimationProps.IMMEDIATE, false /* fromDockGesture */, + true /* dismissRecentsIfAllRemoved */); + } + + // Only callback for the newly added tasks after this stack has been updated + int addedTaskCount = addedTasks.size(); + for (int i = 0; i < addedTaskCount; i++) { + mCb.onStackTaskAdded(this, addedTasks.get(i)); + } + + // Notify that the task stack has been updated + if (notifyStackChanges) { + mCb.onStackTasksUpdated(this); + } + } + + /** + * Gets the front-most task in the stack. + */ + public Task getFrontMostTask() { + ArrayList<Task> stackTasks = mStackTaskList.getTasks(); + if (stackTasks.isEmpty()) { + return null; + } + return stackTasks.get(stackTasks.size() - 1); + } + + /** Gets the task keys */ + public ArrayList<TaskKey> getTaskKeys() { + ArrayList<TaskKey> taskKeys = new ArrayList<>(); + ArrayList<Task> tasks = computeAllTasksList(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + taskKeys.add(task.key); + } + return taskKeys; + } + + /** + * Returns the set of "active" (non-historical) tasks in the stack that have been used recently. + */ + public ArrayList<Task> getTasks() { + return mStackTaskList.getTasks(); + } + + /** + * Computes a set of all the active and historical tasks. + */ + public ArrayList<Task> computeAllTasksList() { + ArrayList<Task> tasks = new ArrayList<>(); + tasks.addAll(mStackTaskList.getTasks()); + return tasks; + } + + /** + * Returns the number of stack tasks. + */ + public int getTaskCount() { + return mStackTaskList.size(); + } + + /** + * Returns the task in stack tasks which is the launch target. + */ + public Task getLaunchTarget() { + ArrayList<Task> tasks = mStackTaskList.getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + if (task.isLaunchTarget) { + return task; + } + } + return null; + } + + /** + * Returns whether the next launch target should actually be the PiP task. + */ + public boolean isNextLaunchTargetPip(long lastPipTime) { + Task launchTarget = getLaunchTarget(); + Task nextLaunchTarget = getNextLaunchTargetRaw(); + if (nextLaunchTarget != null && lastPipTime > 0) { + // If the PiP time is more recent than the next launch target, then launch the PiP task + return lastPipTime > nextLaunchTarget.key.lastActiveTime; + } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) { + // Otherwise, if there is no next launch target, but there is a PiP, then launch + // the PiP task + return true; + } + return false; + } + + /** + * Returns the task in stack tasks which should be launched next if Recents are toggled + * again, or null if there is no task to be launched. Callers should check + * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the + * stack. + */ + public Task getNextLaunchTarget() { + Task nextLaunchTarget = getNextLaunchTargetRaw(); + if (nextLaunchTarget != null) { + return nextLaunchTarget; + } + return getTasks().get(getTaskCount() - 1); + } + + private Task getNextLaunchTargetRaw() { + int taskCount = getTaskCount(); + if (taskCount == 0) { + return null; + } + int launchTaskIndex = indexOfTask(getLaunchTarget()); + if (launchTaskIndex != -1 && launchTaskIndex > 0) { + return getTasks().get(launchTaskIndex - 1); + } + return null; + } + + /** Returns the index of this task in this current task stack */ + public int indexOfTask(Task t) { + return mStackTaskList.indexOf(t); + } + + /** Finds the task with the specified task id. */ + public Task findTaskWithId(int taskId) { + ArrayList<Task> tasks = computeAllTasksList(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + if (task.key.id == taskId) { + return task; + } + } + return null; + } + + /** + * Computes the components of tasks in this stack that have been removed as a result of a change + * in the specified package. + */ + public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) { + // Identify all the tasks that should be removed as a result of the package being removed. + // Using a set to ensure that we callback once per unique component. + ArraySet<ComponentName> existingComponents = new ArraySet<>(); + ArraySet<ComponentName> removedComponents = new ArraySet<>(); + ArrayList<TaskKey> taskKeys = getTaskKeys(); + int taskKeyCount = taskKeys.size(); + for (int i = 0; i < taskKeyCount; i++) { + TaskKey t = taskKeys.get(i); + + // Skip if this doesn't apply to the current user + if (t.userId != userId) continue; + + ComponentName cn = t.getComponent(); + if (cn.getPackageName().equals(packageName)) { + if (existingComponents.contains(cn)) { + // If we know that the component still exists in the package, then skip + continue; + } + if (PackageManagerWrapper.getInstance().getActivityInfo(cn, userId) != null) { + existingComponents.add(cn); + } else { + removedComponents.add(cn); + } + } + } + return removedComponents; + } + + @Override + public String toString() { + String str = "Stack Tasks (" + mStackTaskList.size() + "):\n"; + ArrayList<Task> tasks = mStackTaskList.getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + str += " " + tasks.get(i).toString() + "\n"; + } + return str; + } + + /** + * Given a list of tasks, returns a map of each task's key to the task. + */ + private ArrayMap<TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) { + ArrayMap<TaskKey, Task> map = new ArrayMap<>(tasks.size()); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + map.put(task.key, task); + } + return map; + } + + public void dump(String prefix, PrintWriter writer) { + String innerPrefix = prefix + " "; + + writer.print(prefix); writer.print(TAG); + writer.print(" numStackTasks="); writer.print(mStackTaskList.size()); + writer.println(); + ArrayList<Task> tasks = mStackTaskList.getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + tasks.get(i).dump(innerPrefix, writer); + } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java new file mode 100644 index 000000000000..dd1763bb118b --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 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.shared.recents.model; + +import static android.content.res.Configuration.ORIENTATION_UNDEFINED; + +import android.app.ActivityManager.TaskSnapshot; +import android.graphics.Bitmap; +import android.graphics.Rect; + +/** + * Data for a single thumbnail. + */ +public class ThumbnailData { + + public final Bitmap thumbnail; + public int orientation; + public Rect insets; + public boolean reducedResolution; + public float scale; + + public ThumbnailData() { + thumbnail = null; + orientation = ORIENTATION_UNDEFINED; + insets = new Rect(); + reducedResolution = false; + scale = 1f; + } + + public ThumbnailData(TaskSnapshot snapshot) { + thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot()); + insets = new Rect(snapshot.getContentInsets()); + orientation = snapshot.getOrientation(); + reducedResolution = snapshot.isReducedResolution(); + scale = snapshot.getScale(); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/AnimationProps.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/AnimationProps.java new file mode 100644 index 000000000000..2de7f74ba477 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/AnimationProps.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2016 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.shared.recents.utilities; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.annotation.IntDef; +import android.util.SparseArray; +import android.util.SparseLongArray; +import android.view.View; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; + +/** + * The generic set of animation properties to animate a {@link View}. The animation can have + * different interpolators, start delays and durations for each of the different properties. + */ +public class AnimationProps { + + private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); + public static final AnimationProps IMMEDIATE = new AnimationProps(0, LINEAR_INTERPOLATOR); + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ALL, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, ALPHA, SCALE, BOUNDS}) + public @interface PropType {} + + public static final int ALL = 0; + public static final int TRANSLATION_X = 1; + public static final int TRANSLATION_Y = 2; + public static final int TRANSLATION_Z = 3; + public static final int ALPHA = 4; + public static final int SCALE = 5; + public static final int BOUNDS = 6; + public static final int DIM_ALPHA = 7; + + private SparseLongArray mPropStartDelay; + private SparseLongArray mPropDuration; + private SparseArray<Interpolator> mPropInterpolators; + private Animator.AnimatorListener mListener; + + /** + * The builder constructor. + */ + public AnimationProps() {} + + /** + * Creates an animation with a default {@param duration} and {@param interpolator} for all + * properties in this animation. + */ + public AnimationProps(int duration, Interpolator interpolator) { + this(0, duration, interpolator, null); + } + + /** + * Creates an animation with a default {@param duration} and {@param interpolator} for all + * properties in this animation. + */ + public AnimationProps(int duration, Interpolator interpolator, + Animator.AnimatorListener listener) { + this(0, duration, interpolator, listener); + } + + /** + * Creates an animation with a default {@param startDelay}, {@param duration} and + * {@param interpolator} for all properties in this animation. + */ + public AnimationProps(int startDelay, int duration, Interpolator interpolator) { + this(startDelay, duration, interpolator, null); + } + + /** + * Creates an animation with a default {@param startDelay}, {@param duration} and + * {@param interpolator} for all properties in this animation. + */ + public AnimationProps(int startDelay, int duration, Interpolator interpolator, + Animator.AnimatorListener listener) { + setStartDelay(ALL, startDelay); + setDuration(ALL, duration); + setInterpolator(ALL, interpolator); + setListener(listener); + } + + /** + * Creates a new {@link AnimatorSet} that will animate the given animators. Callers need to + * manually apply the individual animation properties for each of the animators respectively. + */ + public AnimatorSet createAnimator(List<Animator> animators) { + AnimatorSet anim = new AnimatorSet(); + if (mListener != null) { + anim.addListener(mListener); + } + anim.playTogether(animators); + return anim; + } + + /** + * Applies the specific start delay, duration and interpolator to the given {@param animator} + * for the specified {@param propertyType}. + */ + public <T extends ValueAnimator> T apply(@PropType int propertyType, T animator) { + animator.setStartDelay(getStartDelay(propertyType)); + animator.setDuration(getDuration(propertyType)); + animator.setInterpolator(getInterpolator(propertyType)); + return animator; + } + + /** + * Sets a start delay for a specific property. + */ + public AnimationProps setStartDelay(@PropType int propertyType, int startDelay) { + if (mPropStartDelay == null) { + mPropStartDelay = new SparseLongArray(); + } + mPropStartDelay.append(propertyType, startDelay); + return this; + } + + /** + * Returns the start delay for a specific property. + */ + public long getStartDelay(@PropType int propertyType) { + if (mPropStartDelay != null) { + long startDelay = mPropStartDelay.get(propertyType, -1); + if (startDelay != -1) { + return startDelay; + } + return mPropStartDelay.get(ALL, 0); + } + return 0; + } + + /** + * Sets a duration for a specific property. + */ + public AnimationProps setDuration(@PropType int propertyType, int duration) { + if (mPropDuration == null) { + mPropDuration = new SparseLongArray(); + } + mPropDuration.append(propertyType, duration); + return this; + } + + /** + * Returns the duration for a specific property. + */ + public long getDuration(@PropType int propertyType) { + if (mPropDuration != null) { + long duration = mPropDuration.get(propertyType, -1); + if (duration != -1) { + return duration; + } + return mPropDuration.get(ALL, 0); + } + return 0; + } + + /** + * Sets an interpolator for a specific property. + */ + public AnimationProps setInterpolator(@PropType int propertyType, Interpolator interpolator) { + if (mPropInterpolators == null) { + mPropInterpolators = new SparseArray<>(); + } + mPropInterpolators.append(propertyType, interpolator); + return this; + } + + /** + * Returns the interpolator for a specific property, falling back to the general interpolator + * if there is no specific property interpolator. + */ + public Interpolator getInterpolator(@PropType int propertyType) { + if (mPropInterpolators != null) { + Interpolator interp = mPropInterpolators.get(propertyType); + if (interp != null) { + return interp; + } + return mPropInterpolators.get(ALL, LINEAR_INTERPOLATOR); + } + return LINEAR_INTERPOLATOR; + } + + /** + * Sets an animator listener for this animation. + */ + public AnimationProps setListener(Animator.AnimatorListener listener) { + mListener = listener; + return this; + } + + /** + * Returns the animator listener for this animation. + */ + public Animator.AnimatorListener getListener() { + return mListener; + } + + /** + * Returns whether this animation has any duration. + */ + public boolean isImmediate() { + int count = mPropDuration.size(); + for (int i = 0; i < count; i++) { + if (mPropDuration.valueAt(i) > 0) { + return false; + } + } + return true; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/AppTrace.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/AppTrace.java new file mode 100644 index 000000000000..0241c593b850 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/AppTrace.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017 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.shared.recents.utilities; + +import static android.os.Trace.TRACE_TAG_APP; + +/** + * Helper class for internal trace functions. + */ +public class AppTrace { + + /** + * Begins a new async trace section with the given {@param key} and {@param cookie}. + */ + public static void start(String key, int cookie) { + android.os.Trace.asyncTraceBegin(TRACE_TAG_APP, key, cookie); + } + + /** + * Begins a new async trace section with the given {@param key}. + */ + public static void start(String key) { + android.os.Trace.asyncTraceBegin(TRACE_TAG_APP, key, 0); + } + + /** + * Ends an existing async trace section with the given {@param key}. + */ + public static void end(String key) { + android.os.Trace.asyncTraceEnd(TRACE_TAG_APP, key, 0); + } + + /** + * Ends an existing async trace section with the given {@param key} and {@param cookie}. + */ + public static void end(String key, int cookie) { + android.os.Trace.asyncTraceEnd(TRACE_TAG_APP, key, cookie); + } + + /** + * Begins a new trace section with the given {@param key}. Can be nested. + */ + public static void beginSection(String key) { + android.os.Trace.beginSection(key); + } + + /** + * Ends an existing trace section started in the last {@link #beginSection(String)}. + */ + public static void endSection() { + android.os.Trace.endSection(); + } + + /** + * Traces a counter value. + */ + public static void count(String name, int count) { + android.os.Trace.traceCounter(TRACE_TAG_APP, name, count); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/RectFEvaluator.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/RectFEvaluator.java new file mode 100644 index 000000000000..51c1b5aa13d7 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/RectFEvaluator.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016 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.shared.recents.utilities; + +import android.animation.TypeEvaluator; +import android.graphics.RectF; + +/** + * This evaluator can be used to perform type interpolation between <code>RectF</code> values. + */ +public class RectFEvaluator implements TypeEvaluator<RectF> { + + private final RectF mRect = new RectF(); + + /** + * This function returns the result of linearly interpolating the start and + * end Rect values, with <code>fraction</code> representing the proportion + * between the start and end values. The calculation is a simple parametric + * calculation on each of the separate components in the Rect objects + * (left, top, right, and bottom). + * + * <p>The object returned will be the <code>reuseRect</code> passed into the constructor.</p> + * + * @param fraction The fraction from the starting to the ending values + * @param startValue The start Rect + * @param endValue The end Rect + * @return A linear interpolation between the start and end values, given the + * <code>fraction</code> parameter. + */ + @Override + public RectF evaluate(float fraction, RectF startValue, RectF endValue) { + float left = startValue.left + ((endValue.left - startValue.left) * fraction); + float top = startValue.top + ((endValue.top - startValue.top) * fraction); + float right = startValue.right + ((endValue.right - startValue.right) * fraction); + float bottom = startValue.bottom + ((endValue.bottom - startValue.bottom) * fraction); + mRect.set(left, top, right, bottom); + return mRect; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java new file mode 100644 index 000000000000..7d159b745254 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -0,0 +1,337 @@ +/* + * 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.shared.recents.utilities; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.RectEvaluator; +import android.annotation.FloatRange; +import android.annotation.Nullable; +import android.app.Activity; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Message; +import android.os.Trace; +import android.util.ArraySet; +import android.util.IntProperty; +import android.util.Property; +import android.util.TypedValue; +import android.view.Surface; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.ViewRootImpl; +import android.view.ViewStub; + +import java.util.ArrayList; +import java.util.Collections; + +/* Common code */ +public class Utilities { + + public static final Property<Drawable, Integer> DRAWABLE_ALPHA = + new IntProperty<Drawable>("drawableAlpha") { + @Override + public void setValue(Drawable object, int alpha) { + object.setAlpha(alpha); + } + + @Override + public Integer get(Drawable object) { + return object.getAlpha(); + } + }; + + public static final Property<Drawable, Rect> DRAWABLE_RECT = + new Property<Drawable, Rect>(Rect.class, "drawableBounds") { + @Override + public void set(Drawable object, Rect bounds) { + object.setBounds(bounds); + } + + @Override + public Rect get(Drawable object) { + return object.getBounds(); + } + }; + + public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator(); + public static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect()); + + /** + * @return the first parent walking up the view hierarchy that has the given class type. + * + * @param parentClass must be a class derived from {@link View} + */ + public static <T extends View> T findParent(View v, Class<T> parentClass) { + ViewParent parent = v.getParent(); + while (parent != null) { + if (parentClass.isAssignableFrom(parent.getClass())) { + return (T) parent; + } + parent = parent.getParent(); + } + return null; + } + + /** + * Initializes the {@param setOut} with the given object. + */ + public static <T> ArraySet<T> objectToSet(T obj, ArraySet<T> setOut) { + setOut.clear(); + if (obj != null) { + setOut.add(obj); + } + return setOut; + } + + /** + * Replaces the contents of {@param setOut} with the contents of the {@param array}. + */ + public static <T> ArraySet<T> arrayToSet(T[] array, ArraySet<T> setOut) { + setOut.clear(); + if (array != null) { + Collections.addAll(setOut, array); + } + return setOut; + } + + /** + * @return the clamped {@param value} between the provided {@param min} and {@param max}. + */ + public static float clamp(float value, float min, float max) { + return Math.max(min, Math.min(max, value)); + } + + /** + * @return the clamped {@param value} between the provided {@param min} and {@param max}. + */ + public static int clamp(int value, int min, int max) { + return Math.max(min, Math.min(max, value)); + } + + /** + * @return the clamped {@param value} between 0 and 1. + */ + public static float clamp01(float value) { + return Math.max(0f, Math.min(1f, value)); + } + + /** + * Scales the {@param value} to be proportionally between the {@param min} and + * {@param max} values. + * + * @param value must be between 0 and 1 + */ + public static float mapRange(@FloatRange(from=0.0,to=1.0) float value, float min, float max) { + return min + (value * (max - min)); + } + + /** + * Scales the {@param value} proportionally from {@param min} and {@param max} to 0 and 1. + * + * @param value must be between {@param min} and {@param max} + */ + public static float unmapRange(float value, float min, float max) { + return (value - min) / (max - min); + } + + /** Scales a rect about its centroid */ + public static void scaleRectAboutCenter(RectF r, float scale) { + if (scale != 1.0f) { + float cx = r.centerX(); + float cy = r.centerY(); + r.offset(-cx, -cy); + r.left *= scale; + r.top *= scale; + r.right *= scale; + r.bottom *= scale; + r.offset(cx, cy); + } + } + + /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */ + public static float computeContrastBetweenColors(int bg, int fg) { + float bgR = Color.red(bg) / 255f; + float bgG = Color.green(bg) / 255f; + float bgB = Color.blue(bg) / 255f; + bgR = (bgR < 0.03928f) ? bgR / 12.92f : (float) Math.pow((bgR + 0.055f) / 1.055f, 2.4f); + bgG = (bgG < 0.03928f) ? bgG / 12.92f : (float) Math.pow((bgG + 0.055f) / 1.055f, 2.4f); + bgB = (bgB < 0.03928f) ? bgB / 12.92f : (float) Math.pow((bgB + 0.055f) / 1.055f, 2.4f); + float bgL = 0.2126f * bgR + 0.7152f * bgG + 0.0722f * bgB; + + float fgR = Color.red(fg) / 255f; + float fgG = Color.green(fg) / 255f; + float fgB = Color.blue(fg) / 255f; + fgR = (fgR < 0.03928f) ? fgR / 12.92f : (float) Math.pow((fgR + 0.055f) / 1.055f, 2.4f); + fgG = (fgG < 0.03928f) ? fgG / 12.92f : (float) Math.pow((fgG + 0.055f) / 1.055f, 2.4f); + fgB = (fgB < 0.03928f) ? fgB / 12.92f : (float) Math.pow((fgB + 0.055f) / 1.055f, 2.4f); + float fgL = 0.2126f * fgR + 0.7152f * fgG + 0.0722f * fgB; + + return Math.abs((fgL + 0.05f) / (bgL + 0.05f)); + } + + /** Returns the base color overlaid with another overlay color with a specified alpha. */ + public static int getColorWithOverlay(int baseColor, int overlayColor, float overlayAlpha) { + return Color.rgb( + (int) (overlayAlpha * Color.red(baseColor) + + (1f - overlayAlpha) * Color.red(overlayColor)), + (int) (overlayAlpha * Color.green(baseColor) + + (1f - overlayAlpha) * Color.green(overlayColor)), + (int) (overlayAlpha * Color.blue(baseColor) + + (1f - overlayAlpha) * Color.blue(overlayColor))); + } + + /** + * Cancels an animation ensuring that if it has listeners, onCancel and onEnd + * are not called. + */ + public static void cancelAnimationWithoutCallbacks(Animator animator) { + if (animator != null && animator.isStarted()) { + removeAnimationListenersRecursive(animator); + animator.cancel(); + } + } + + /** + * Recursively removes all the listeners of all children of this animator + */ + public static void removeAnimationListenersRecursive(Animator animator) { + if (animator instanceof AnimatorSet) { + ArrayList<Animator> animators = ((AnimatorSet) animator).getChildAnimations(); + for (int i = animators.size() - 1; i >= 0; i--) { + removeAnimationListenersRecursive(animators.get(i)); + } + } + animator.removeAllListeners(); + } + + /** + * Sets the given {@link View}'s frame from its current translation. + */ + public static void setViewFrameFromTranslation(View v) { + RectF taskViewRect = new RectF(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); + taskViewRect.offset(v.getTranslationX(), v.getTranslationY()); + v.setTranslationX(0); + v.setTranslationY(0); + v.setLeftTopRightBottom((int) taskViewRect.left, (int) taskViewRect.top, + (int) taskViewRect.right, (int) taskViewRect.bottom); + } + + /** + * Returns a view stub for the given view id. + */ + public static ViewStub findViewStubById(View v, int stubId) { + return (ViewStub) v.findViewById(stubId); + } + + /** + * Returns a view stub for the given view id. + */ + public static ViewStub findViewStubById(Activity a, int stubId) { + return (ViewStub) a.findViewById(stubId); + } + + /** + * Used for debugging, converts DP to PX. + */ + public static float dpToPx(Resources res, float dp) { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics()); + } + + /** + * Adds a trace event for debugging. + */ + public static void addTraceEvent(String event) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, event); + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + + /** + * Returns whether this view, or one of its descendants have accessibility focus. + */ + public static boolean isDescendentAccessibilityFocused(View v) { + if (v.isAccessibilityFocused()) { + return true; + } + + if (v instanceof ViewGroup) { + ViewGroup vg = (ViewGroup) v; + int childCount = vg.getChildCount(); + for (int i = 0; i < childCount; i++) { + if (isDescendentAccessibilityFocused(vg.getChildAt(i))) { + return true; + } + } + } + return false; + } + + /** + * Returns the application configuration, which is independent of the activity's current + * configuration in multiwindow. + */ + public static Configuration getAppConfiguration(Context context) { + return context.getApplicationContext().getResources().getConfiguration(); + } + + /** + * @return The next frame name for the specified surface or -1 if the surface is no longer + * valid. + */ + public static long getNextFrameNumber(Surface s) { + return s != null && s.isValid() + ? s.getNextFrameNumber() + : -1; + + } + + /** + * @return The surface for the specified view. + */ + public static @Nullable Surface getSurface(View v) { + ViewRootImpl viewRoot = v.getViewRootImpl(); + if (viewRoot == null) { + return null; + } + return viewRoot.mSurface; + } + + /** + * Returns a lightweight dump of a rect. + */ + public static String dumpRect(Rect r) { + if (r == null) { + return "N:0,0-0,0"; + } + return r.left + "," + r.top + "-" + r.right + "," + r.bottom; + } + + /** + * Posts a runnable on a handler at the front of the queue ignoring any sync barriers. + */ + public static void postAtFrontOfQueueAsynchronously(Handler h, Runnable r) { + Message msg = h.obtainMessage().setCallback(r); + h.sendMessageAtFrontOfQueue(msg); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AnimateableViewBounds.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AnimateableViewBounds.java new file mode 100644 index 000000000000..45728c403ac4 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AnimateableViewBounds.java @@ -0,0 +1,136 @@ +/* + * 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.shared.recents.view; + +import android.graphics.Outline; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewDebug; +import android.view.ViewOutlineProvider; + +import com.android.systemui.shared.recents.utilities.Utilities; + +/** + * An outline provider that has a clip and outline that can be animated. + */ +public class AnimateableViewBounds extends ViewOutlineProvider { + + private static final float MIN_ALPHA = 0.1f; + private static final float MAX_ALPHA = 0.8f; + + protected View mSourceView; + protected Rect mClipRect = new Rect(); + protected Rect mClipBounds = new Rect(); + protected Rect mLastClipBounds = new Rect(); + protected int mCornerRadius; + protected float mAlpha = 1f; + + public AnimateableViewBounds(View source, int cornerRadius) { + mSourceView = source; + mCornerRadius = cornerRadius; + } + + /** + * Resets the right and bottom clip for this view. + */ + public void reset() { + mClipRect.set(0, 0, 0, 0); + updateClipBounds(); + } + + @Override + public void getOutline(View view, Outline outline) { + outline.setAlpha(Utilities.mapRange(mAlpha, MIN_ALPHA, MAX_ALPHA)); + if (mCornerRadius > 0) { + outline.setRoundRect(mClipRect.left, mClipRect.top, + mSourceView.getWidth() - mClipRect.right, + mSourceView.getHeight() - mClipRect.bottom, + mCornerRadius); + } else { + outline.setRect(mClipRect.left, mClipRect.top, + mSourceView.getWidth() - mClipRect.right, + mSourceView.getHeight() - mClipRect.bottom); + } + } + + /** + * Sets the view outline alpha. + */ + public void setAlpha(float alpha) { + if (Float.compare(alpha, mAlpha) != 0) { + mAlpha = alpha; + // TODO, If both clip and alpha change in the same frame, only invalidate once + mSourceView.invalidateOutline(); + } + } + + /** + * @return the outline alpha. + */ + public float getAlpha() { + return mAlpha; + } + + /** + * Sets the top clip. + */ + public void setClipTop(int top) { + mClipRect.top = top; + updateClipBounds(); + } + + /** + * @return the top clip. + */ + public int getClipTop() { + return mClipRect.top; + } + + /** + * Sets the bottom clip. + */ + public void setClipBottom(int bottom) { + mClipRect.bottom = bottom; + updateClipBounds(); + } + + /** + * @return the bottom clip. + */ + public int getClipBottom() { + return mClipRect.bottom; + } + + /** + * @return the clip bounds. + */ + public Rect getClipBounds() { + return mClipBounds; + } + + protected void updateClipBounds() { + mClipBounds.set(Math.max(0, mClipRect.left), Math.max(0, mClipRect.top), + mSourceView.getWidth() - Math.max(0, mClipRect.right), + mSourceView.getHeight() - Math.max(0, mClipRect.bottom)); + if (!mLastClipBounds.equals(mClipBounds)) { + mSourceView.setClipBounds(mClipBounds); + // TODO, If both clip and alpha change in the same frame, only invalidate once + mSourceView.invalidateOutline(); + mLastClipBounds.set(mClipBounds); + } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecCompat.java new file mode 100644 index 000000000000..ebdc884285d2 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecCompat.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 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.shared.recents.view; + +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.view.AppTransitionAnimationSpec; + +/** + * Wraps the internal app transition animation spec. + */ +public class AppTransitionAnimationSpecCompat { + + private int mTaskId; + private Bitmap mBuffer; + private Rect mRect; + + public AppTransitionAnimationSpecCompat(int taskId, Bitmap buffer, Rect rect) { + mTaskId = taskId; + mBuffer = buffer; + mRect = rect; + } + + public AppTransitionAnimationSpec toAppTransitionAnimationSpec() { + return new AppTransitionAnimationSpec(mTaskId, + mBuffer != null ? mBuffer.createGraphicBufferHandle() : null, mRect); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java new file mode 100644 index 000000000000..85d362a7a767 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 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.shared.recents.view; + +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.view.AppTransitionAnimationSpec; +import android.view.IAppTransitionAnimationSpecsFuture; + +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; + +/** + * To be implemented by a particular animation to asynchronously provide the animation specs for a + * particular transition. + */ +public abstract class AppTransitionAnimationSpecsFuture { + + private final Handler mHandler; + private FutureTask<List<AppTransitionAnimationSpecCompat>> mComposeTask = new FutureTask<>( + new Callable<List<AppTransitionAnimationSpecCompat>>() { + @Override + public List<AppTransitionAnimationSpecCompat> call() throws Exception { + return composeSpecs(); + } + }); + + private final IAppTransitionAnimationSpecsFuture mFuture = + new IAppTransitionAnimationSpecsFuture.Stub() { + @Override + public AppTransitionAnimationSpec[] get() throws RemoteException { + try { + if (!mComposeTask.isDone()) { + mHandler.post(mComposeTask); + } + List<AppTransitionAnimationSpecCompat> specs = mComposeTask.get(); + if (specs == null) { + return null; + } + + AppTransitionAnimationSpec[] arr = new AppTransitionAnimationSpec[specs.size()]; + for (int i = 0; i < specs.size(); i++) { + arr[i] = specs.get(i).toAppTransitionAnimationSpec(); + } + return arr; + } catch (Exception e) { + return null; + } + } + }; + + public AppTransitionAnimationSpecsFuture(Handler handler) { + mHandler = handler; + } + + /** + * Returns the future to handle the call from window manager. + */ + public final IAppTransitionAnimationSpecsFuture getFuture() { + return mFuture; + } + + /** + * Called ahead of the future callback to compose the specs to be returned in the future. + */ + public final void composeSpecsSynchronous() { + if (Looper.myLooper() != mHandler.getLooper()) { + throw new RuntimeException("composeSpecsSynchronous() called from wrong looper"); + } + mComposeTask.run(); + } + + public abstract List<AppTransitionAnimationSpecCompat> composeSpecs(); +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java new file mode 100644 index 000000000000..ab890439a381 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2017 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.shared.recents.view; + +import android.app.ActivityOptions; +import android.app.ActivityOptions.OnAnimationStartedListener; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.GraphicBuffer; +import android.os.Bundle; +import android.os.Handler; +import android.os.IRemoteCallback; +import android.os.RemoteException; +import android.view.DisplayListCanvas; +import android.view.RenderNode; +import android.view.ThreadedRenderer; +import android.view.View; + +import java.util.function.Consumer; + +/** + * A helper class to create transitions to/from an App to Recents. + */ +public class RecentsTransition { + + /** + * Creates a new transition aspect scaled transition activity options. + */ + public static ActivityOptions createAspectScaleAnimation(Context context, Handler handler, + boolean scaleUp, AppTransitionAnimationSpecsFuture animationSpecsFuture, + final Runnable animationStartCallback) { + final OnAnimationStartedListener animStartedListener = new OnAnimationStartedListener() { + private boolean mHandled; + + @Override + public void onAnimationStarted() { + // OnAnimationStartedListener can be called numerous times, so debounce here to + // prevent multiple callbacks + if (mHandled) { + return; + } + mHandled = true; + + if (animationStartCallback != null) { + animationStartCallback.run(); + } + } + }; + final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation( + context, handler, + animationSpecsFuture != null ? animationSpecsFuture.getFuture() : null, + animStartedListener, scaleUp); + return opts; + } + + /** + * Wraps a animation-start callback in a binder that can be called from window manager. + */ + public static IRemoteCallback wrapStartedListener(final Handler handler, + final Runnable animationStartCallback) { + if (animationStartCallback == null) { + return null; + } + return new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) throws RemoteException { + handler.post(animationStartCallback); + } + }; + } + + /** + * @return a {@link GraphicBuffer} with the {@param view} drawn into it. Result can be null if + * we were unable to allocate a hardware bitmap. + */ + public static Bitmap drawViewIntoHardwareBitmap(int width, int height, final View view, + final float scale, final int eraseColor) { + return createHardwareBitmap(width, height, new Consumer<Canvas>() { + @Override + public void accept(Canvas c) { + c.scale(scale, scale); + if (eraseColor != 0) { + c.drawColor(eraseColor); + } + if (view != null) { + view.draw(c); + } + } + }); + } + + /** + * @return a hardware {@link Bitmap} after being drawn with the {@param consumer}. Result can be + * null if we were unable to allocate a hardware bitmap. + */ + public static Bitmap createHardwareBitmap(int width, int height, Consumer<Canvas> consumer) { + RenderNode node = RenderNode.create("RecentsTransition", null); + node.setLeftTopRightBottom(0, 0, width, height); + node.setClipToBounds(false); + DisplayListCanvas c = node.start(width, height); + consumer.accept(c); + node.end(c); + return ThreadedRenderer.createHardwareBitmap(node, width, height); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java new file mode 100644 index 000000000000..0d8ce58d55fd --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2018 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.shared.system; + +import android.app.Activity; + +public class ActivityCompat { + private final Activity mWrapped; + + public ActivityCompat(Activity activity) { + mWrapped = activity; + } + + /** + * @see Activity#registerRemoteAnimations + */ + public void registerRemoteAnimations(RemoteAnimationDefinitionCompat definition) { + mWrapped.registerRemoteAnimations(definition.getWrapped()); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java new file mode 100644 index 000000000000..138910cb9820 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -0,0 +1,484 @@ +/* + * Copyright (C) 2015 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.shared.system; + +import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; +import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; +import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; + +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; +import android.app.ActivityOptions; +import android.app.AppGlobals; +import android.app.IAssistDataReceiver; +import android.app.WindowConfiguration.ActivityType; +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.PackageManager; +import android.content.pm.UserInfo; +import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.IconDrawableFactory; +import android.util.Log; +import android.view.IRecentsAnimationController; +import android.view.IRecentsAnimationRunner; + +import android.view.RemoteAnimationTarget; +import android.view.WindowManagerGlobal; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.Task.TaskKey; +import com.android.systemui.shared.recents.model.ThumbnailData; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class ActivityManagerWrapper { + + private static final String TAG = "ActivityManagerWrapper"; + + private static final ActivityManagerWrapper sInstance = new ActivityManagerWrapper(); + + private final PackageManager mPackageManager; + private final IconDrawableFactory mDrawableFactory; + private final BackgroundExecutor mBackgroundExecutor; + private final TaskStackChangeListeners mTaskStackChangeListeners; + + private ActivityManagerWrapper() { + final Context context = AppGlobals.getInitialApplication(); + mPackageManager = context.getPackageManager(); + mDrawableFactory = IconDrawableFactory.newInstance(context); + mBackgroundExecutor = BackgroundExecutor.get(); + mTaskStackChangeListeners = new TaskStackChangeListeners(Looper.getMainLooper()); + } + + public static ActivityManagerWrapper getInstance() { + return sInstance; + } + + /** + * @return the current user's id. + */ + public int getCurrentUserId() { + UserInfo ui; + try { + ui = ActivityManager.getService().getCurrentUser(); + return ui != null ? ui.id : 0; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return the top running task (can be {@code null}). + */ + public ActivityManager.RunningTaskInfo getRunningTask() { + return getRunningTask(ACTIVITY_TYPE_RECENTS /* ignoreActivityType */); + } + + public ActivityManager.RunningTaskInfo getRunningTask(@ActivityType int ignoreActivityType) { + // Note: The set of running tasks from the system is ordered by recency + try { + List<ActivityManager.RunningTaskInfo> tasks = + ActivityManager.getService().getFilteredTasks(1, ignoreActivityType, + WINDOWING_MODE_PINNED /* ignoreWindowingMode */); + if (tasks.isEmpty()) { + return null; + } + return tasks.get(0); + } catch (RemoteException e) { + return null; + } + } + + /** + * @return a list of the recents tasks. + */ + public List<RecentTaskInfo> getRecentTasks(int numTasks, int userId) { + try { + return ActivityManager.getService().getRecentTasks(numTasks, + RECENT_IGNORE_UNAVAILABLE, userId).getList(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to get recent tasks", e); + return new ArrayList<>(); + } + } + + /** + * @return the task snapshot for the given {@param taskId}. + */ + public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean reducedResolution) { + ActivityManager.TaskSnapshot snapshot = null; + try { + snapshot = ActivityManager.getService().getTaskSnapshot(taskId, reducedResolution); + } catch (RemoteException e) { + Log.w(TAG, "Failed to retrieve task snapshot", e); + } + if (snapshot != null) { + return new ThumbnailData(snapshot); + } else { + return new ThumbnailData(); + } + } + + /** + * @return the task description icon, loading and badging it if it necessary. + */ + public Drawable getBadgedTaskDescriptionIcon(Context context, + ActivityManager.TaskDescription taskDescription, int userId, Resources res) { + Bitmap tdIcon = taskDescription.getInMemoryIcon(); + Drawable dIcon = null; + if (tdIcon != null) { + dIcon = new BitmapDrawable(res, tdIcon); + } else if (taskDescription.getIconResource() != 0) { + try { + dIcon = context.getDrawable(taskDescription.getIconResource()); + } catch (NotFoundException e) { + Log.e(TAG, "Could not find icon drawable from resource", e); + } + } else { + tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon( + taskDescription.getIconFilename(), userId); + if (tdIcon != null) { + dIcon = new BitmapDrawable(res, tdIcon); + } + } + if (dIcon != null) { + return getBadgedIcon(dIcon, userId); + } + return null; + } + + /** + * @return the given icon for a user, badging if necessary. + */ + private Drawable getBadgedIcon(Drawable icon, int userId) { + if (userId != UserHandle.myUserId()) { + icon = mPackageManager.getUserBadgedIcon(icon, new UserHandle(userId)); + } + return icon; + } + + /** + * @return the activity icon for the ActivityInfo for a user, badging if necessary. + */ + public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) { + return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId); + } + + /** + * @return the application icon for the ApplicationInfo for a user, badging if necessary. + */ + public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) { + return mDrawableFactory.getBadgedIcon(appInfo, userId); + } + + /** + * @return the activity label, badging if necessary. + */ + public String getBadgedActivityLabel(ActivityInfo info, int userId) { + return getBadgedLabel(info.loadLabel(mPackageManager).toString(), userId); + } + + /** + * @return the application label, badging if necessary. + */ + public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) { + return getBadgedLabel(appInfo.loadLabel(mPackageManager).toString(), userId); + } + + /** + * @return 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, + ActivityManager.TaskDescription td) { + String activityLabel; + if (td != null && td.getLabel() != null) { + activityLabel = td.getLabel(); + } else { + activityLabel = info.loadLabel(mPackageManager).toString(); + } + String applicationLabel = info.applicationInfo.loadLabel(mPackageManager).toString(); + String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId); + return applicationLabel.equals(activityLabel) + ? badgedApplicationLabel + : badgedApplicationLabel + " " + activityLabel; + } + + /** + * @return the given label for a user, badging if necessary. + */ + private String getBadgedLabel(String label, int userId) { + if (userId != UserHandle.myUserId()) { + label = mPackageManager.getUserBadgedLabel(label, new UserHandle(userId)).toString(); + } + return label; + } + + /** + * Starts the recents activity. The caller should manage the thread on which this is called. + */ + public void startRecentsActivity(Intent intent, AssistDataReceiver assistDataReceiver, + RecentsAnimationListener animationHandler, Consumer<Boolean> resultCallback, + Handler resultCallbackHandler) { + try { + IAssistDataReceiver receiver = null; + if (assistDataReceiver != null) { + receiver = new IAssistDataReceiver.Stub() { + public void onHandleAssistData(Bundle resultData) { + assistDataReceiver.onHandleAssistData(resultData); + } + public void onHandleAssistScreenshot(Bitmap screenshot) { + assistDataReceiver.onHandleAssistScreenshot(screenshot); + } + }; + } + IRecentsAnimationRunner runner = null; + if (animationHandler != null) { + runner = new IRecentsAnimationRunner.Stub() { + public void onAnimationStart(IRecentsAnimationController controller, + RemoteAnimationTarget[] apps) { + final Rect stableInsets = new Rect(); + WindowManagerWrapper.getInstance().getStableInsets(stableInsets); + onAnimationStart_New(controller, apps, stableInsets, null); + } + + public void onAnimationStart_New(IRecentsAnimationController controller, + RemoteAnimationTarget[] apps, Rect homeContentInsets, + Rect minimizedHomeBounds) { + final RecentsAnimationControllerCompat controllerCompat = + new RecentsAnimationControllerCompat(controller); + final RemoteAnimationTargetCompat[] appsCompat = + RemoteAnimationTargetCompat.wrap(apps); + animationHandler.onAnimationStart(controllerCompat, appsCompat, + homeContentInsets, minimizedHomeBounds); + } + + public void onAnimationCanceled() { + animationHandler.onAnimationCanceled(); + } + }; + } + ActivityManager.getService().startRecentsActivity(intent, receiver, runner); + if (resultCallback != null) { + resultCallbackHandler.post(new Runnable() { + @Override + public void run() { + resultCallback.accept(true); + } + }); + } + } catch (Exception e) { + if (resultCallback != null) { + resultCallbackHandler.post(new Runnable() { + @Override + public void run() { + resultCallback.accept(false); + } + }); + } + } + } + + /** + * Cancels the remote recents animation started from {@link #startRecentsActivity}. + */ + public void cancelRecentsAnimation() { + try { + ActivityManager.getService().cancelRecentsAnimation(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to cancel recents animation", e); + } + } + + /** + * Starts a task from Recents. + * + * @see {@link #startActivityFromRecentsAsync(TaskKey, ActivityOptions, int, int, Consumer, Handler)} + */ + public void startActivityFromRecentsAsync(Task.TaskKey taskKey, ActivityOptions options, + Consumer<Boolean> resultCallback, Handler resultCallbackHandler) { + startActivityFromRecentsAsync(taskKey, options, WINDOWING_MODE_UNDEFINED, + ACTIVITY_TYPE_UNDEFINED, resultCallback, resultCallbackHandler); + } + + /** + * Starts a task from Recents. + * + * @param resultCallback The result success callback + * @param resultCallbackHandler The handler to receive the result callback + */ + public void startActivityFromRecentsAsync(Task.TaskKey taskKey, ActivityOptions options, + int windowingMode, int activityType, Consumer<Boolean> resultCallback, + Handler resultCallbackHandler) { + if (taskKey.windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { + // 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.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + } else if (windowingMode != WINDOWING_MODE_UNDEFINED + || activityType != ACTIVITY_TYPE_UNDEFINED) { + if (options == null) { + options = ActivityOptions.makeBasic(); + } + options.setLaunchWindowingMode(windowingMode); + options.setLaunchActivityType(activityType); + } + final ActivityOptions finalOptions = options; + + // Execute this from another thread such that we can do other things (like caching the + // bitmap for the thumbnail) while AM is busy starting our activity. + mBackgroundExecutor.submit(new Runnable() { + @Override + public void run() { + boolean result = false; + try { + result = startActivityFromRecents(taskKey.id, finalOptions); + } catch (Exception e) { + // Fall through + } + final boolean finalResult = result; + if (resultCallback != null) { + resultCallbackHandler.post(new Runnable() { + @Override + public void run() { + resultCallback.accept(finalResult); + } + }); + } + } + }); + } + + /** + * Starts a task from Recents synchronously. + */ + public boolean startActivityFromRecents(int taskId, ActivityOptions options) { + try { + Bundle optsBundle = options == null ? null : options.toBundle(); + ActivityManager.getService().startActivityFromRecents(taskId, optsBundle); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * Registers a task stack listener with the system. + * This should be called on the main thread. + */ + public void registerTaskStackListener(TaskStackChangeListener listener) { + synchronized (mTaskStackChangeListeners) { + mTaskStackChangeListeners.addListener(ActivityManager.getService(), listener); + } + } + + /** + * Unregisters a task stack listener with the system. + * This should be called on the main thread. + */ + public void unregisterTaskStackListener(TaskStackChangeListener listener) { + synchronized (mTaskStackChangeListeners) { + mTaskStackChangeListeners.removeListener(listener); + } + } + + /** + * Requests that the system close any open system windows (including other SystemUI). + */ + public void closeSystemWindows(String reason) { + mBackgroundExecutor.submit(new Runnable() { + @Override + public void run() { + try { + ActivityManager.getService().closeSystemDialogs(reason); + } catch (RemoteException e) { + Log.w(TAG, "Failed to close system windows", e); + } + } + }); + } + + /** + * Removes a task by id. + */ + public void removeTask(int taskId) { + mBackgroundExecutor.submit(new Runnable() { + @Override + public void run() { + try { + ActivityManager.getService().removeTask(taskId); + } catch (RemoteException e) { + Log.w(TAG, "Failed to remove task=" + taskId, e); + } + } + }); + } + + /** + * Cancels the current window transtion to/from Recents for the given task id. + */ + public void cancelWindowTransition(int taskId) { + try { + ActivityManager.getService().cancelTaskWindowTransition(taskId); + } catch (RemoteException e) { + Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e); + } + } + + /** + * @return whether there is currently a locked task (ie. in screen pinning). + */ + public boolean isLockToAppActive() { + try { + return ActivityManager.getService().getLockTaskModeState() != LOCK_TASK_MODE_NONE; + } catch (RemoteException e) { + return false; + } + } + + /** + * @return whether screen pinning is enabled. + */ + public boolean isLockToAppEnabled() { + final ContentResolver cr = AppGlobals.getInitialApplication().getContentResolver(); + return Settings.System.getInt(cr, Settings.System.LOCK_TO_APP_ENABLED, 0) != 0; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java new file mode 100644 index 000000000000..712cca67c5d6 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 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.shared.system; + +import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT; +import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; + +import android.app.ActivityOptions; + +/** + * Wrapper around internal ActivityOptions creation. + */ +public abstract class ActivityOptionsCompat { + + /** + * @return ActivityOptions for starting a task in split screen. + */ + public static ActivityOptions makeSplitScreenOptions(boolean dockTopLeft) { + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + options.setSplitScreenCreateMode(dockTopLeft + ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT + : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT); + return options; + } + + public static ActivityOptions makeRemoteAnimation( + RemoteAnimationAdapterCompat remoteAnimationAdapter) { + return ActivityOptions.makeRemoteAnimation(remoteAnimationAdapter.getWrapped()); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/AssistDataReceiver.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/AssistDataReceiver.java new file mode 100644 index 000000000000..7cd6c512b660 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/AssistDataReceiver.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017 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.shared.system; + +import android.graphics.Bitmap; +import android.os.Bundle; + +/** + * Abstract class for assist data receivers. + */ +public abstract class AssistDataReceiver { + public void onHandleAssistData(Bundle resultData) {} + public void onHandleAssistScreenshot(Bitmap screenshot) {} +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/BackgroundExecutor.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/BackgroundExecutor.java new file mode 100644 index 000000000000..0bd89a78cfda --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/BackgroundExecutor.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 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.shared.system; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + * Offloads work from other threads by running it in a background thread. + */ +public class BackgroundExecutor { + + private static final BackgroundExecutor sInstance = new BackgroundExecutor(); + + private final ExecutorService mExecutorService = Executors.newFixedThreadPool(2); + + /** + * @return the static instance of the background executor. + */ + public static BackgroundExecutor get() { + return sInstance; + } + + /** + * Runs the given {@param callable} on one of the background executor threads. + */ + public <T> Future<T> submit(Callable<T> callable) { + return mExecutorService.submit(callable); + } + + /** + * Runs the given {@param runnable} on one of the background executor threads. + */ + public Future<?> submit(Runnable runnable) { + return mExecutorService.submit(runnable); + } + + /** + * Runs the given {@param runnable} on one of the background executor threads. Return + * {@param result} when the future is resolved. + */ + public <T> Future<T> submit(Runnable runnable, T result) { + return mExecutorService.submit(runnable, result); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ChoreographerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ChoreographerCompat.java new file mode 100644 index 000000000000..4d422bb8a2bd --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ChoreographerCompat.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 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.shared.system; + +import static android.view.Choreographer.CALLBACK_INPUT; + +import android.view.Choreographer; + +/** + * Wraps the internal choreographer. + */ +public class ChoreographerCompat { + + /** + * Posts an input callback to the choreographer. + */ + public static void postInputFrame(Choreographer choreographer, Runnable runnable) { + choreographer.postCallback(CALLBACK_INPUT, runnable, null); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.aidl new file mode 100644 index 000000000000..f9450adcdf30 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2017 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.shared.system; + +parcelable GraphicBufferCompat;
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.java new file mode 100644 index 000000000000..66b8fed1a48f --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 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.shared.system; + +import android.graphics.Bitmap; +import android.graphics.GraphicBuffer; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Wraps the internal graphic buffer. + */ +public class GraphicBufferCompat implements Parcelable { + + private GraphicBuffer mBuffer; + + public GraphicBufferCompat(GraphicBuffer buffer) { + mBuffer = buffer; + } + + public GraphicBufferCompat(Parcel in) { + mBuffer = GraphicBuffer.CREATOR.createFromParcel(in); + } + + public Bitmap toBitmap() { + return mBuffer != null + ? Bitmap.createHardwareBitmap(mBuffer) + : null; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mBuffer.writeToParcel(dest, flags); + } + + public static final Parcelable.Creator<GraphicBufferCompat> CREATOR + = new Parcelable.Creator<GraphicBufferCompat>() { + public GraphicBufferCompat createFromParcel(Parcel in) { + return new GraphicBufferCompat(in); + } + + public GraphicBufferCompat[] newArray(int size) { + return new GraphicBufferCompat[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java new file mode 100644 index 000000000000..38b8ae8418af --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2017 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.shared.system; + +import static android.view.WindowManager.INPUT_CONSUMER_PIP; +import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; + +import android.os.Binder; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import android.view.BatchedInputEventReceiver; +import android.view.Choreographer; +import android.view.InputChannel; +import android.view.InputEvent; +import android.view.IWindowManager; +import android.view.MotionEvent; +import android.view.WindowManagerGlobal; + +import java.io.PrintWriter; + +/** + * Manages the input consumer that allows the SystemUI to directly receive touch input. + */ +public class InputConsumerController { + + private static final String TAG = InputConsumerController.class.getSimpleName(); + + /** + * Listener interface for callers to subscribe to touch events. + */ + public interface TouchListener { + boolean onTouchEvent(MotionEvent ev); + } + + /** + * Listener interface for callers to learn when this class is registered or unregistered with + * window manager + */ + public interface RegistrationListener { + void onRegistrationChanged(boolean isRegistered); + } + + /** + * Input handler used for the input consumer. Input events are batched and consumed with the + * SurfaceFlinger vsync. + */ + private final class InputEventReceiver extends BatchedInputEventReceiver { + + public InputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper, Choreographer.getSfInstance()); + } + + @Override + public void onInputEvent(InputEvent event, int displayId) { + boolean handled = true; + try { + if (mListener != null && event instanceof MotionEvent) { + MotionEvent ev = (MotionEvent) event; + handled = mListener.onTouchEvent(ev); + } + } finally { + finishInputEvent(event, handled); + } + } + } + + private final IWindowManager mWindowManager; + private final IBinder mToken; + private final String mName; + + private InputEventReceiver mInputEventReceiver; + private TouchListener mListener; + private RegistrationListener mRegistrationListener; + + /** + * @param name the name corresponding to the input consumer that is defined in the system. + */ + public InputConsumerController(IWindowManager windowManager, String name) { + mWindowManager = windowManager; + mToken = new Binder(); + mName = name; + } + + /** + * @return A controller for the pip input consumer. + */ + public static InputConsumerController getPipInputConsumer() { + return new InputConsumerController(WindowManagerGlobal.getWindowManagerService(), + INPUT_CONSUMER_PIP); + } + + /** + * @return A controller for the recents animation input consumer. + */ + public static InputConsumerController getRecentsAnimationInputConsumer() { + return new InputConsumerController(WindowManagerGlobal.getWindowManagerService(), + INPUT_CONSUMER_RECENTS_ANIMATION); + } + + /** + * Sets the touch listener. + */ + public void setTouchListener(TouchListener listener) { + mListener = listener; + } + + /** + * Sets the registration listener. + */ + public void setRegistrationListener(RegistrationListener listener) { + mRegistrationListener = listener; + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null); + } + } + + /** + * Check if the InputConsumer is currently registered with WindowManager + * + * @return {@code true} if registered, {@code false} if not. + */ + public boolean isRegistered() { + return mInputEventReceiver != null; + } + + /** + * Registers the input consumer. + */ + public void registerInputConsumer() { + if (mInputEventReceiver == null) { + final InputChannel inputChannel = new InputChannel(); + try { + mWindowManager.destroyInputConsumer(mName); + mWindowManager.createInputConsumer(mToken, mName, inputChannel); + } catch (RemoteException e) { + Log.e(TAG, "Failed to create input consumer", e); + } + mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper()); + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(true /* isRegistered */); + } + } + } + + /** + * Unregisters the input consumer. + */ + public void unregisterInputConsumer() { + if (mInputEventReceiver != null) { + try { + mWindowManager.destroyInputConsumer(mName); + } catch (RemoteException e) { + Log.e(TAG, "Failed to destroy input consumer", e); + } + mInputEventReceiver.dispose(); + mInputEventReceiver = null; + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(false /* isRegistered */); + } + } + } + + public void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + TAG); + pw.println(innerPrefix + "registered=" + (mInputEventReceiver != null)); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PackageManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PackageManagerWrapper.java new file mode 100644 index 000000000000..6fa7db3f2cdb --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PackageManagerWrapper.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2015 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.shared.system; + +import android.app.AppGlobals; +import android.content.ComponentName; +import android.content.pm.ActivityInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.RemoteException; + +import java.util.ArrayList; +import java.util.List; + +public class PackageManagerWrapper { + + private static final String TAG = "PackageManagerWrapper"; + + private static final PackageManagerWrapper sInstance = new PackageManagerWrapper(); + + private static final IPackageManager mIPackageManager = AppGlobals.getPackageManager(); + + public static PackageManagerWrapper getInstance() { + return sInstance; + } + + /** + * @return the activity info for a given {@param componentName} and {@param userId}. + */ + public ActivityInfo getActivityInfo(ComponentName componentName, int userId) { + try { + return mIPackageManager.getActivityInfo(componentName, PackageManager.GET_META_DATA, + userId); + } catch (RemoteException e) { + e.printStackTrace(); + return null; + } + } + + /** + * @return true if the packageName belongs to the current preferred home app on the device. + * + * If will also return false if there are multiple home apps and the user has not picked any + * preferred home, in which case the user would see a disambiguation screen on going to home. + */ + public boolean isDefaultHomeActivity(String packageName) { + List<ResolveInfo> allHomeCandidates = new ArrayList<>(); + ComponentName home; + try { + home = mIPackageManager.getHomeActivities(allHomeCandidates); + } catch (RemoteException e) { + e.printStackTrace(); + return false; + } + + if (home != null && packageName.equals(home.getPackageName())) { + return true; + } + + // Find the launcher with the highest priority and return that component if there are no + // other home activity with the same priority. + int lastPriority = Integer.MIN_VALUE; + ComponentName lastComponent = null; + final int size = allHomeCandidates.size(); + for (int i = 0; i < size; i++) { + final ResolveInfo ri = allHomeCandidates.get(i); + if (ri.priority > lastPriority) { + lastComponent = ri.activityInfo.getComponentName(); + lastPriority = ri.priority; + } else if (ri.priority == lastPriority) { + // Two components found with same priority. + lastComponent = null; + } + } + return lastComponent != null && packageName.equals(lastComponent.getPackageName()); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java new file mode 100644 index 000000000000..9a7abf82c56c --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018 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.shared.system; + +import android.app.ActivityManager.TaskSnapshot; +import android.os.RemoteException; +import android.util.Log; +import android.view.IRecentsAnimationController; + +import com.android.systemui.shared.recents.model.ThumbnailData; + +public class RecentsAnimationControllerCompat { + + private static final String TAG = RecentsAnimationControllerCompat.class.getSimpleName(); + + private IRecentsAnimationController mAnimationController; + + public RecentsAnimationControllerCompat(IRecentsAnimationController animationController) { + mAnimationController = animationController; + } + + public ThumbnailData screenshotTask(int taskId) { + try { + TaskSnapshot snapshot = mAnimationController.screenshotTask(taskId); + return snapshot != null ? new ThumbnailData(snapshot) : new ThumbnailData(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to screenshot task", e); + return new ThumbnailData(); + } + } + + public void setInputConsumerEnabled(boolean enabled) { + try { + mAnimationController.setInputConsumerEnabled(enabled); + } catch (RemoteException e) { + Log.e(TAG, "Failed to set input consumer enabled state", e); + } + } + + public void finish(boolean toHome) { + try { + mAnimationController.finish(toHome); + } catch (RemoteException e) { + Log.e(TAG, "Failed to finish recents animation", e); + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java new file mode 100644 index 000000000000..a473db1a7a14 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 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.shared.system; + +import android.graphics.Rect; + +public interface RecentsAnimationListener { + + /** + * Called when the animation into Recents can start. This call is made on the binder thread. + */ + void onAnimationStart(RecentsAnimationControllerCompat controller, + RemoteAnimationTargetCompat[] apps, Rect homeContentInsets, Rect minimizedHomeBounds); + + /** + * Called when the animation into Recents was canceled. This call is made on the binder thread. + */ + void onAnimationCanceled(); +}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java new file mode 100644 index 000000000000..625b1de74290 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2018 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.shared.system; + +import android.os.RemoteException; +import android.util.Log; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationTarget; + +/** + * @see RemoteAnimationAdapter + */ +public class RemoteAnimationAdapterCompat { + + private final RemoteAnimationAdapter mWrapped; + + public RemoteAnimationAdapterCompat(RemoteAnimationRunnerCompat runner, long duration, + long statusBarTransitionDelay) { + mWrapped = new RemoteAnimationAdapter(wrapRemoteAnimationRunner(runner), duration, + statusBarTransitionDelay); + } + + RemoteAnimationAdapter getWrapped() { + return mWrapped; + } + + private static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner( + RemoteAnimationRunnerCompat remoteAnimationAdapter) { + return new IRemoteAnimationRunner.Stub() { + @Override + public void onAnimationStart(RemoteAnimationTarget[] apps, + IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { + final RemoteAnimationTargetCompat[] appsCompat = + RemoteAnimationTargetCompat.wrap(apps); + final Runnable animationFinishedCallback = new Runnable() { + @Override + public void run() { + try { + finishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" + + " finished callback", e); + } + } + }; + remoteAnimationAdapter.onAnimationStart(appsCompat, animationFinishedCallback); + } + + @Override + public void onAnimationCancelled() throws RemoteException { + remoteAnimationAdapter.onAnimationCancelled(); + } + }; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java new file mode 100644 index 000000000000..5fff5febec85 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 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.shared.system; + +import android.view.RemoteAnimationDefinition; + +/** + * @see RemoteAnimationDefinition + */ +public class RemoteAnimationDefinitionCompat { + + private final RemoteAnimationDefinition mWrapped = new RemoteAnimationDefinition(); + + public void addRemoteAnimation(int transition, RemoteAnimationAdapterCompat adapter) { + mWrapped.addRemoteAnimation(transition, adapter.getWrapped()); + } + + RemoteAnimationDefinition getWrapped() { + return mWrapped; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java new file mode 100644 index 000000000000..5a85df967197 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2018 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.shared.system; + +public interface RemoteAnimationRunnerCompat { + void onAnimationStart(RemoteAnimationTargetCompat[] apps, Runnable finishedCallback); + void onAnimationCancelled(); +}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java new file mode 100644 index 000000000000..b8c5049d1c1d --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 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.shared.system; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; + +import android.app.WindowConfiguration; +import android.graphics.Point; +import android.graphics.Rect; +import android.view.RemoteAnimationTarget; + +/** + * @see RemoteAnimationTarget + */ +public class RemoteAnimationTargetCompat { + + public static final int MODE_OPENING = RemoteAnimationTarget.MODE_OPENING; + public static final int MODE_CLOSING = RemoteAnimationTarget.MODE_CLOSING; + + public final int taskId; + public final int mode; + public final SurfaceControlCompat leash; + public final boolean isTranslucent; + public final Rect clipRect; + public final int prefixOrderIndex; + public final Point position; + public final Rect sourceContainerBounds; + + private final RemoteAnimationTarget mTarget; + + public RemoteAnimationTargetCompat(RemoteAnimationTarget app) { + mTarget = app; + taskId = app.taskId; + mode = app.mode; + leash = new SurfaceControlCompat(app.leash); + isTranslucent = app.isTranslucent; + clipRect = app.clipRect; + position = app.position; + sourceContainerBounds = app.sourceContainerBounds; + prefixOrderIndex = app.prefixOrderIndex; + } + + public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) { + final RemoteAnimationTargetCompat[] appsCompat = + new RemoteAnimationTargetCompat[apps.length]; + for (int i = 0; i < apps.length; i++) { + appsCompat[i] = new RemoteAnimationTargetCompat(apps[i]); + } + return appsCompat; + } + + /** + * TODO: Get as a method for compatibility (will move into ctor once Launcher updates) + */ + public Rect getContentInsets() { + return mTarget.contentInsets; + } + + /** + * TODO: Get as a method for compatibility (will move into ctor once Launcher updates) + */ + public boolean isAssistantActivityType() { + return mTarget.windowConfiguration.getActivityType() == ACTIVITY_TYPE_ASSISTANT; + } +}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java new file mode 100644 index 000000000000..cd12141c3268 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 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.shared.system; + +import android.view.SurfaceControl; + +public class SurfaceControlCompat { + SurfaceControl mSurfaceControl; + + public SurfaceControlCompat(SurfaceControl surfaceControl) { + mSurfaceControl = surfaceControl; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java new file mode 100644 index 000000000000..7db3ac6e3889 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 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.shared.system; + +import android.app.ActivityManager.TaskSnapshot; +import android.os.UserHandle; +import android.util.Log; + +import com.android.systemui.shared.recents.model.ThumbnailData; + +/** + * An interface to track task stack changes. Classes should implement this instead of + * {@link android.app.ITaskStackListener} to reduce IPC calls from system services. + */ +public abstract class TaskStackChangeListener { + + // Binder thread callbacks + public void onTaskStackChangedBackground() { } + + // Main thread callbacks + public void onTaskStackChanged() { } + public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) { } + public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { } + public void onActivityUnpinned() { } + public void onPinnedActivityRestartAttempt(boolean clearedTask) { } + public void onPinnedStackAnimationStarted() { } + public void onPinnedStackAnimationEnded() { } + public void onActivityForcedResizable(String packageName, int taskId, int reason) { } + public void onActivityDismissingDockedStack() { } + public void onActivityLaunchOnSecondaryDisplayFailed() { } + public void onTaskProfileLocked(int taskId, int userId) { } + public void onTaskRemoved(int taskId) { } + public void onTaskMovedToFront(int taskId) { } + public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { } + + /** + * Checks that the current user matches the process. Since + * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of + * {@link TaskStackChangeListener} should make this call to verify that we don't act on events + * originating from another user's interactions. + */ + protected final boolean checkCurrentUserId(int currentUserId, boolean debug) { + int processUserId = UserHandle.myUserId(); + if (processUserId != currentUserId) { + if (debug) { + Log.d("TaskStackChangeListener", "UID mismatch. Process is uid=" + processUserId + + " and the current user is uid=" + currentUserId); + } + return false; + } + return true; + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java new file mode 100644 index 000000000000..857e0eadb1bc --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2017 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.shared.system; + +import android.app.ActivityManager.TaskSnapshot; +import android.app.IActivityManager; +import android.app.TaskStackListener; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.Trace; +import android.util.Log; + +import com.android.systemui.shared.recents.model.ThumbnailData; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tracks all the task stack listeners + */ +public class TaskStackChangeListeners extends TaskStackListener { + + private static final String TAG = TaskStackChangeListeners.class.getSimpleName(); + + /** + * List of {@link TaskStackChangeListener} registered from {@link #addListener}. + */ + private final List<TaskStackChangeListener> mTaskStackListeners = new ArrayList<>(); + private final List<TaskStackChangeListener> mTmpListeners = new ArrayList<>(); + + private final Handler mHandler; + private boolean mRegistered; + + public TaskStackChangeListeners(Looper looper) { + mHandler = new H(looper); + } + + public void addListener(IActivityManager am, TaskStackChangeListener listener) { + mTaskStackListeners.add(listener); + if (!mRegistered) { + // Register mTaskStackListener to IActivityManager only once if needed. + try { + am.registerTaskStackListener(this); + mRegistered = true; + } catch (Exception e) { + Log.w(TAG, "Failed to call registerTaskStackListener", e); + } + } + } + + public void removeListener(TaskStackChangeListener listener) { + mTaskStackListeners.remove(listener); + } + + @Override + public void onTaskStackChanged() throws RemoteException { + // Call the task changed callback for the non-ui thread listeners first + synchronized (mTaskStackListeners) { + mTmpListeners.clear(); + mTmpListeners.addAll(mTaskStackListeners); + } + for (int i = mTmpListeners.size() - 1; i >= 0; i--) { + mTmpListeners.get(i).onTaskStackChangedBackground(); + } + + mHandler.removeMessages(H.ON_TASK_STACK_CHANGED); + mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED); + } + + @Override + public void onActivityPinned(String packageName, int userId, int taskId, int stackId) + throws RemoteException { + mHandler.removeMessages(H.ON_ACTIVITY_PINNED); + mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, + new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget(); + } + + @Override + public void onActivityUnpinned() throws RemoteException { + mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED); + mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED); + } + + @Override + public void onPinnedActivityRestartAttempt(boolean clearedTask) + throws RemoteException{ + mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT); + mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, clearedTask ? 1 : 0, 0) + .sendToTarget(); + } + + @Override + public void onPinnedStackAnimationStarted() throws RemoteException { + mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED); + mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED); + } + + @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, int reason) + throws RemoteException { + mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName) + .sendToTarget(); + } + + @Override + public void onActivityDismissingDockedStack() throws RemoteException { + mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK); + } + + @Override + public void onActivityLaunchOnSecondaryDisplayFailed() throws RemoteException { + mHandler.sendEmptyMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED); + } + + @Override + public void onTaskProfileLocked(int taskId, int userId) throws RemoteException { + mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget(); + } + + @Override + public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) + throws RemoteException { + mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget(); + } + + @Override + public void onTaskRemoved(int taskId) throws RemoteException { + mHandler.obtainMessage(H.ON_TASK_REMOVED, taskId, 0).sendToTarget(); + } + + @Override + public void onTaskMovedToFront(int taskId) throws RemoteException { + mHandler.obtainMessage(H.ON_TASK_MOVED_TO_FRONT, taskId, 0).sendToTarget(); + } + + @Override + public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) + throws RemoteException { + mHandler.obtainMessage(H.ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE, taskId, + requestedOrientation).sendToTarget(); + } + + private final class H extends Handler { + private static final int ON_TASK_STACK_CHANGED = 1; + private static final int ON_TASK_SNAPSHOT_CHANGED = 2; + private static final int ON_ACTIVITY_PINNED = 3; + private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 4; + private static final int ON_PINNED_STACK_ANIMATION_ENDED = 5; + private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6; + private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7; + private static final int ON_TASK_PROFILE_LOCKED = 8; + private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9; + private static final int ON_ACTIVITY_UNPINNED = 10; + private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11; + private static final int ON_TASK_REMOVED = 12; + private static final int ON_TASK_MOVED_TO_FRONT = 13; + private static final int ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE = 14; + + + public H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + synchronized (mTaskStackListeners) { + switch (msg.what) { + case ON_TASK_STACK_CHANGED: { + Trace.beginSection("onTaskStackChanged"); + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskStackChanged(); + } + Trace.endSection(); + break; + } + case ON_TASK_SNAPSHOT_CHANGED: { + Trace.beginSection("onTaskSnapshotChanged"); + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1, + new ThumbnailData((TaskSnapshot) msg.obj)); + } + Trace.endSection(); + break; + } + case ON_ACTIVITY_PINNED: { + final PinnedActivityInfo info = (PinnedActivityInfo) msg.obj; + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityPinned( + info.mPackageName, info.mUserId, info.mTaskId, info.mStackId); + } + break; + } + case ON_ACTIVITY_UNPINNED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityUnpinned(); + } + break; + } + case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onPinnedActivityRestartAttempt( + msg.arg1 != 0); + } + break; + } + case ON_PINNED_STACK_ANIMATION_STARTED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onPinnedStackAnimationStarted(); + } + 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, msg.arg2); + } + break; + } + case ON_ACTIVITY_DISMISSING_DOCKED_STACK: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityDismissingDockedStack(); + } + break; + } + case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityLaunchOnSecondaryDisplayFailed(); + } + break; + } + case ON_TASK_PROFILE_LOCKED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2); + } + break; + } + case ON_TASK_REMOVED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskRemoved(msg.arg1); + } + break; + } + case ON_TASK_MOVED_TO_FRONT: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskMovedToFront(msg.arg1); + } + break; + } + case ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityRequestedOrientationChanged( + msg.arg1, msg.arg2); + } + break; + } + } + } + } + } + + private static class PinnedActivityInfo { + final String mPackageName; + final int mUserId; + final int mTaskId; + final int mStackId; + + PinnedActivityInfo(String packageName, int userId, int taskId, int stackId) { + mPackageName = packageName; + mUserId = userId; + mTaskId = taskId; + mStackId = stackId; + } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java new file mode 100644 index 000000000000..c82c5191b127 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 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.shared.system; + +import android.graphics.Matrix; +import android.graphics.Rect; +import android.os.IBinder; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; + +public class TransactionCompat { + + private final Transaction mTransaction; + + private final float[] mTmpValues = new float[9]; + + public TransactionCompat() { + mTransaction = new Transaction(); + } + + public void apply() { + mTransaction.apply(); + } + + public TransactionCompat show(SurfaceControlCompat surfaceControl) { + mTransaction.show(surfaceControl.mSurfaceControl); + return this; + } + + public TransactionCompat hide(SurfaceControlCompat surfaceControl) { + mTransaction.hide(surfaceControl.mSurfaceControl); + return this; + } + + public TransactionCompat setPosition(SurfaceControlCompat surfaceControl, float x, float y) { + mTransaction.setPosition(surfaceControl.mSurfaceControl, x, y); + return this; + } + + public TransactionCompat setSize(SurfaceControlCompat surfaceControl, int w, int h) { + mTransaction.setSize(surfaceControl.mSurfaceControl, w, h); + return this; + } + + public TransactionCompat setLayer(SurfaceControlCompat surfaceControl, int z) { + mTransaction.setLayer(surfaceControl.mSurfaceControl, z); + return this; + } + + public TransactionCompat setAlpha(SurfaceControlCompat surfaceControl, float alpha) { + mTransaction.setAlpha(surfaceControl.mSurfaceControl, alpha); + return this; + } + + public TransactionCompat setMatrix(SurfaceControlCompat surfaceControl, float dsdx, float dtdx, + float dtdy, float dsdy) { + mTransaction.setMatrix(surfaceControl.mSurfaceControl, dsdx, dtdx, dtdy, dsdy); + return this; + } + + public TransactionCompat setMatrix(SurfaceControlCompat surfaceControl, Matrix matrix) { + mTransaction.setMatrix(surfaceControl.mSurfaceControl, matrix, mTmpValues); + return this; + } + + public TransactionCompat setWindowCrop(SurfaceControlCompat surfaceControl, Rect crop) { + mTransaction.setWindowCrop(surfaceControl.mSurfaceControl, crop); + return this; + } + + public TransactionCompat setFinalCrop(SurfaceControlCompat surfaceControl, Rect crop) { + mTransaction.setFinalCrop(surfaceControl.mSurfaceControl, crop); + return this; + } + + public TransactionCompat deferTransactionUntil(SurfaceControlCompat surfaceControl, + IBinder handle, long frameNumber) { + mTransaction.deferTransactionUntil(surfaceControl.mSurfaceControl, handle, frameNumber); + return this; + } + + public TransactionCompat deferTransactionUntil(SurfaceControlCompat surfaceControl, + Surface barrier, long frameNumber) { + mTransaction.deferTransactionUntilSurface(surfaceControl.mSurfaceControl, barrier, + frameNumber); + return this; + } + + public TransactionCompat setColor(SurfaceControlCompat surfaceControl, float[] color) { + mTransaction.setColor(surfaceControl.mSurfaceControl, color); + return this; + } +}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java new file mode 100644 index 000000000000..68400fc977df --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017 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.shared.system; + +import static android.view.Display.DEFAULT_DISPLAY; + +import android.graphics.Rect; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; +import android.view.RemoteAnimationAdapter; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; + +import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; +import com.android.systemui.shared.recents.view.RecentsTransition; + +public class WindowManagerWrapper { + + private static final String TAG = "WindowManagerWrapper"; + + public static final int TRANSIT_UNSET = WindowManager.TRANSIT_UNSET; + public static final int TRANSIT_NONE = WindowManager.TRANSIT_NONE; + public static final int TRANSIT_ACTIVITY_OPEN = WindowManager.TRANSIT_ACTIVITY_OPEN; + public static final int TRANSIT_ACTIVITY_CLOSE = WindowManager.TRANSIT_ACTIVITY_CLOSE; + public static final int TRANSIT_TASK_OPEN = WindowManager.TRANSIT_TASK_OPEN; + public static final int TRANSIT_TASK_CLOSE = WindowManager.TRANSIT_TASK_CLOSE; + public static final int TRANSIT_TASK_TO_FRONT = WindowManager.TRANSIT_TASK_TO_FRONT; + public static final int TRANSIT_TASK_TO_BACK = WindowManager.TRANSIT_TASK_TO_BACK; + public static final int TRANSIT_WALLPAPER_CLOSE = WindowManager.TRANSIT_WALLPAPER_CLOSE; + public static final int TRANSIT_WALLPAPER_OPEN = WindowManager.TRANSIT_WALLPAPER_OPEN; + public static final int TRANSIT_WALLPAPER_INTRA_OPEN = + WindowManager.TRANSIT_WALLPAPER_INTRA_OPEN; + public static final int TRANSIT_WALLPAPER_INTRA_CLOSE = + WindowManager.TRANSIT_WALLPAPER_INTRA_CLOSE; + public static final int TRANSIT_TASK_OPEN_BEHIND = WindowManager.TRANSIT_TASK_OPEN_BEHIND; + public static final int TRANSIT_TASK_IN_PLACE = WindowManager.TRANSIT_TASK_IN_PLACE; + public static final int TRANSIT_ACTIVITY_RELAUNCH = WindowManager.TRANSIT_ACTIVITY_RELAUNCH; + public static final int TRANSIT_DOCK_TASK_FROM_RECENTS = + WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS; + public static final int TRANSIT_KEYGUARD_GOING_AWAY = WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; + public static final int TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER = + WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER; + public static final int TRANSIT_KEYGUARD_OCCLUDE = WindowManager.TRANSIT_KEYGUARD_OCCLUDE; + public static final int TRANSIT_KEYGUARD_UNOCCLUDE = WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; + + private static final WindowManagerWrapper sInstance = new WindowManagerWrapper(); + + public static WindowManagerWrapper getInstance() { + return sInstance; + } + + /** + * @return the stable insets for the primary display. + */ + public void getStableInsets(Rect outStableInsets) { + try { + WindowManagerGlobal.getWindowManagerService().getStableInsets(DEFAULT_DISPLAY, + outStableInsets); + } catch (RemoteException e) { + Log.e(TAG, "Failed to get stable insets", e); + } + } + + /** + * Overrides a pending app transition. + */ + public void overridePendingAppTransitionMultiThumbFuture( + AppTransitionAnimationSpecsFuture animationSpecFuture, + Runnable animStartedCallback, Handler animStartedCallbackHandler, boolean scaleUp) { + try { + WindowManagerGlobal.getWindowManagerService() + .overridePendingAppTransitionMultiThumbFuture(animationSpecFuture.getFuture(), + RecentsTransition.wrapStartedListener(animStartedCallbackHandler, + animStartedCallback), scaleUp); + } catch (RemoteException e) { + Log.w(TAG, "Failed to override pending app transition (multi-thumbnail future): ", e); + } + } + + public void overridePendingAppTransitionRemote( + RemoteAnimationAdapterCompat remoteAnimationAdapter) { + try { + WindowManagerGlobal.getWindowManagerService().overridePendingAppTransitionRemote( + remoteAnimationAdapter.getWrapped()); + } catch (RemoteException e) { + Log.w(TAG, "Failed to override pending app transition (remote): ", e); + } + } +} |