summaryrefslogtreecommitdiff
path: root/packages/SystemUI/shared/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SystemUI/shared/src')
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl60
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl47
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java186
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/FilteredTaskList.java124
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java236
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java207
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoader.java460
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java295
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskFilter.java27
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyCache.java89
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyLruCache.java96
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java71
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java60
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskStack.java395
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java51
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/AnimationProps.java229
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/AppTrace.java73
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/RectFEvaluator.java52
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java337
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AnimateableViewBounds.java136
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecCompat.java41
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java89
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java119
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java34
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java484
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java46
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/AssistDataReceiver.java28
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/BackgroundExecutor.java61
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ChoreographerCompat.java33
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.aidl19
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/GraphicBufferCompat.java64
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java184
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/PackageManagerWrapper.java92
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java61
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java33
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java71
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java35
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java22
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java79
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceControlCompat.java27
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java67
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java302
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java108
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java104
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);
+ }
+ }
+}