summaryrefslogtreecommitdiffstats
path: root/packages/SystemUI/src/com/android/systemui/recents/misc
diff options
context:
space:
mode:
authorWinson Chung <winsonc@google.com>2014-07-03 15:54:42 -0700
committerWinson Chung <winsonc@google.com>2014-07-07 17:33:31 -0700
commitffa2ec664479bff6b4b61d4c349d9db2cb37ca16 (patch)
tree512f872a6317f7f4e90083a56a796fb1dca21d5e /packages/SystemUI/src/com/android/systemui/recents/misc
parent95f621a1a4a0891075f1f9daf8e0323aab488793 (diff)
downloadframeworks_base-ffa2ec664479bff6b4b61d4c349d9db2cb37ca16.zip
frameworks_base-ffa2ec664479bff6b4b61d4c349d9db2cb37ca16.tar.gz
frameworks_base-ffa2ec664479bff6b4b61d4c349d9db2cb37ca16.tar.bz2
Refactoring to support groups.
- Removing RecentService, determining animations just in time - Fixing a few issues with animations of newly picked up tasks from the pool - Moving helper classes into sub package Change-Id: Ie10385d1f9ca79eea918b16932f56b60e2802304
Diffstat (limited to 'packages/SystemUI/src/com/android/systemui/recents/misc')
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/Console.java196
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/DebugTrigger.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java85
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java105
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java424
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java99
7 files changed, 1015 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Console.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Console.java
new file mode 100644
index 0000000..28ac9d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Console.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+
+import android.content.ComponentCallbacks2;
+import android.content.Context;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.widget.Toast;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+public class Console {
+ // Timer
+ public static final Map<Object, Long> mTimeLogs = new HashMap<Object, Long>();
+
+ // Colors
+ public static final String AnsiReset = "\u001B[0m";
+ public static final String AnsiBlack = "\u001B[30m";
+ public static final String AnsiRed = "\u001B[31m"; // SystemUIHandshake
+ public static final String AnsiGreen = "\u001B[32m"; // MeasureAndLayout
+ public static final String AnsiYellow = "\u001B[33m"; // SynchronizeViewsWithModel
+ public static final String AnsiBlue = "\u001B[34m"; // TouchEvents, Search
+ public static final String AnsiPurple = "\u001B[35m"; // Draw
+ public static final String AnsiCyan = "\u001B[36m"; // ClickEvents
+ public static final String AnsiWhite = "\u001B[37m";
+
+ // Console enabled state
+ public static boolean Enabled = false;
+
+ /** Logs a key */
+ public static void log(String key) {
+ log(true, key, "", AnsiReset);
+ }
+
+ /** Logs a conditioned key */
+ public static void log(boolean condition, String key) {
+ if (condition) {
+ log(condition, key, "", AnsiReset);
+ }
+ }
+
+ /** Logs a key in a specific color */
+ public static void log(boolean condition, String key, Object data) {
+ if (condition) {
+ log(condition, key, data, AnsiReset);
+ }
+ }
+
+ /** Logs a key with data in a specific color */
+ public static void log(boolean condition, String key, Object data, String color) {
+ if (condition) {
+ System.out.println(color + key + AnsiReset + " " + data.toString());
+ }
+ }
+
+ /** Logs an error */
+ public static void logError(Context context, String msg) {
+ Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
+ Log.e("Recents", msg);
+ }
+
+ /** Logs a raw error */
+ public static void logRawError(String msg, Exception e) {
+ Log.e("Recents", msg, e);
+ }
+
+ /** Logs a divider bar */
+ public static void logDivider(boolean condition) {
+ if (condition) {
+ System.out.println("==== [" + System.currentTimeMillis() +
+ "] ============================================================");
+ }
+ }
+
+ /** Starts a time trace */
+ public static void logStartTracingTime(boolean condition, String key) {
+ if (condition) {
+ long curTime = System.currentTimeMillis();
+ mTimeLogs.put(key, curTime);
+ Console.log(condition, "[Recents|" + key + "]",
+ "started @ " + curTime);
+ }
+ }
+
+ /** Continues a time trace */
+ public static void logTraceTime(boolean condition, String key, String desc) {
+ if (condition) {
+ long timeDiff = System.currentTimeMillis() - mTimeLogs.get(key);
+ Console.log(condition, "[Recents|" + key + "|" + desc + "]",
+ "+" + timeDiff + "ms");
+ }
+ }
+
+ /** Logs a stack trace */
+ public static void logStackTrace() {
+ logStackTrace("", 99);
+ }
+
+ /** Logs a stack trace to a certain depth */
+ public static void logStackTrace(int depth) {
+ logStackTrace("", depth);
+ }
+
+ /** Logs a stack trace to a certain depth with a key */
+ public static void logStackTrace(String key, int depth) {
+ int offset = 0;
+ StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
+ String tinyStackTrace = "";
+ // Skip over the known stack trace classes
+ for (int i = 0; i < callStack.length; i++) {
+ StackTraceElement el = callStack[i];
+ String className = el.getClassName();
+ if (className.indexOf("dalvik.system.VMStack") == -1 &&
+ className.indexOf("java.lang.Thread") == -1 &&
+ className.indexOf("recents.Console") == -1) {
+ break;
+ } else {
+ offset++;
+ }
+ }
+ // Build the pretty stack trace
+ int start = Math.min(offset + depth, callStack.length);
+ int end = offset;
+ String indent = "";
+ for (int i = start - 1; i >= end; i--) {
+ StackTraceElement el = callStack[i];
+ tinyStackTrace += indent + " -> " + el.getClassName() +
+ "[" + el.getLineNumber() + "]." + el.getMethodName();
+ if (i > end) {
+ tinyStackTrace += "\n";
+ indent += " ";
+ }
+ }
+ log(true, key, tinyStackTrace, AnsiRed);
+ }
+
+
+ /** Returns the stringified MotionEvent action */
+ public static String motionEventActionToString(int action) {
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ return "Down";
+ case MotionEvent.ACTION_UP:
+ return "Up";
+ case MotionEvent.ACTION_MOVE:
+ return "Move";
+ case MotionEvent.ACTION_CANCEL:
+ return "Cancel";
+ case MotionEvent.ACTION_POINTER_DOWN:
+ return "Pointer Down";
+ case MotionEvent.ACTION_POINTER_UP:
+ return "Pointer Up";
+ default:
+ return "" + action;
+ }
+ }
+
+ public static String trimMemoryLevelToString(int level) {
+ switch (level) {
+ case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
+ return "UI Hidden";
+ case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
+ return "Running Moderate";
+ case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
+ return "Background";
+ case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
+ return "Running Low";
+ case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
+ return "Moderate";
+ case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
+ return "Critical";
+ case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
+ return "Complete";
+ default:
+ return "" + level;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/DebugTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/DebugTrigger.java
new file mode 100644
index 0000000..d000985
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/DebugTrigger.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+import android.os.Handler;
+import android.os.SystemClock;
+import android.view.KeyEvent;
+
+/**
+ * A trigger for catching a debug chord.
+ * We currently use volume up then volume down to trigger this mode.
+ */
+public class DebugTrigger {
+
+ Handler mHandler;
+ Runnable mTriggeredRunnable;
+
+ int mLastKeyCode;
+ long mLastKeyCodeTime;
+
+ public DebugTrigger(Runnable triggeredRunnable) {
+ mHandler = new Handler();
+ mTriggeredRunnable = triggeredRunnable;
+ }
+
+ /** Resets the debug trigger */
+ void reset() {
+ mLastKeyCode = 0;
+ mLastKeyCodeTime = 0;
+ }
+
+ /**
+ * Processes a key event and tests if it is a part of the trigger. If the chord is complete,
+ * then we just call the callback.
+ */
+ public void onKeyEvent(int keyCode) {
+ if (mLastKeyCode == 0) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ mLastKeyCode = keyCode;
+ mLastKeyCodeTime = SystemClock.uptimeMillis();
+ return;
+ }
+ } else {
+ if (mLastKeyCode == KeyEvent.KEYCODE_VOLUME_UP &&
+ keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+ if ((SystemClock.uptimeMillis() - mLastKeyCodeTime) < 750) {
+ mTriggeredRunnable.run();
+ }
+ }
+ }
+ reset();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
new file mode 100644
index 0000000..4456066
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+import android.os.Handler;
+
+/**
+ * A dozer is a class that fires a trigger after it falls asleep. You can occasionally poke it to
+ * wake it up, but it will fall asleep if left untouched.
+ */
+public class DozeTrigger {
+
+ Handler mHandler;
+
+ boolean mIsDozing;
+ boolean mHasTriggered;
+ int mDozeDurationSeconds;
+ Runnable mSleepRunnable;
+
+ // Sleep-runnable
+ Runnable mDozeRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mSleepRunnable.run();
+ mIsDozing = false;
+ mHasTriggered = true;
+ }
+ };
+
+ public DozeTrigger(int dozeDurationSeconds, Runnable sleepRunnable) {
+ mHandler = new Handler();
+ mDozeDurationSeconds = dozeDurationSeconds;
+ mSleepRunnable = sleepRunnable;
+ }
+
+ /** Starts dozing. This also resets the trigger flag. */
+ public void startDozing() {
+ forcePoke();
+ mHasTriggered = false;
+ }
+
+ /** Stops dozing. */
+ public void stopDozing() {
+ mHandler.removeCallbacks(mDozeRunnable);
+ mIsDozing = false;
+ }
+
+ /** Poke this dozer to wake it up for a little bit, if it is dozing. */
+ public void poke() {
+ if (mIsDozing) {
+ forcePoke();
+ }
+ }
+
+ /** Poke this dozer to wake it up for a little bit. */
+ void forcePoke() {
+ mHandler.removeCallbacks(mDozeRunnable);
+ mHandler.postDelayed(mDozeRunnable, mDozeDurationSeconds * 1000);
+ mIsDozing = true;
+ }
+
+ /** Returns whether we are dozing or not. */
+ public boolean isDozing() {
+ return mIsDozing;
+ }
+
+ /** Returns whether the trigger has fired at least once. */
+ public boolean hasTriggered() {
+ return mHasTriggered;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java b/packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java
new file mode 100644
index 0000000..ec3c39c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+/**
+ * Used to generate successive incremented names.
+ */
+public class NamedCounter {
+
+ int mCount;
+ String mPrefix = "";
+ String mSuffix = "";
+
+ public NamedCounter(String prefix, String suffix) {
+ mPrefix = prefix;
+ mSuffix = suffix;
+ }
+
+ /** Returns the next name. */
+ public String nextName() {
+ String name = mPrefix + mCount + mSuffix;
+ mCount++;
+ return name;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
new file mode 100644
index 0000000..31825af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+
+/**
+ * A ref counted trigger that does some logic when the count is first incremented, or last
+ * decremented. Not thread safe as it's not currently needed.
+ */
+public class ReferenceCountedTrigger {
+
+ Context mContext;
+ int mCount;
+ ArrayList<Runnable> mFirstIncRunnables = new ArrayList<Runnable>();
+ ArrayList<Runnable> mLastDecRunnables = new ArrayList<Runnable>();
+ Runnable mErrorRunnable;
+
+ // Convenience runnables
+ Runnable mIncrementRunnable = new Runnable() {
+ @Override
+ public void run() {
+ increment();
+ }
+ };
+ Runnable mDecrementRunnable = new Runnable() {
+ @Override
+ public void run() {
+ decrement();
+ }
+ };
+
+ public ReferenceCountedTrigger(Context context, Runnable firstIncRunnable,
+ Runnable lastDecRunnable, Runnable errorRunanable) {
+ mContext = context;
+ if (firstIncRunnable != null) mFirstIncRunnables.add(firstIncRunnable);
+ if (lastDecRunnable != null) mLastDecRunnables.add(lastDecRunnable);
+ mErrorRunnable = errorRunanable;
+ }
+
+ /** Increments the ref count */
+ public void increment() {
+ if (mCount == 0 && !mFirstIncRunnables.isEmpty()) {
+ int numRunnables = mFirstIncRunnables.size();
+ for (int i = 0; i < numRunnables; i++) {
+ mFirstIncRunnables.get(i).run();
+ }
+ }
+ mCount++;
+ }
+
+ /** Convenience method to increment this trigger as a runnable */
+ public Runnable incrementAsRunnable() {
+ return mIncrementRunnable;
+ }
+
+ /** Adds a runnable to the last-decrement runnables list. */
+ public void addLastDecrementRunnable(Runnable r) {
+ mLastDecRunnables.add(r);
+ }
+
+ /** Decrements the ref count */
+ public void decrement() {
+ mCount--;
+ if (mCount == 0 && !mLastDecRunnables.isEmpty()) {
+ int numRunnables = mLastDecRunnables.size();
+ for (int i = 0; i < numRunnables; i++) {
+ mLastDecRunnables.get(i).run();
+ }
+ } else if (mCount < 0) {
+ if (mErrorRunnable != null) {
+ mErrorRunnable.run();
+ } else {
+ new Throwable("Invalid ref count").printStackTrace();
+ Console.logError(mContext, "Invalid ref count");
+ }
+ }
+ }
+
+ /** Convenience method to decrement this trigger as a runnable */
+ public Runnable decrementAsRunnable() {
+ return mDecrementRunnable;
+ }
+
+ /** Returns the current ref count */
+ public int getCount() {
+ return mCount;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
new file mode 100644
index 0000000..05c0f58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.AppGlobals;
+import android.app.SearchManager;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+import android.util.Pair;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import com.android.systemui.recents.Constants;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Acts as a shim around the real system services that we need to access data from, and provides
+ * a point of injection when testing UI.
+ */
+public class SystemServicesProxy {
+ final static String TAG = "SystemServicesProxy";
+
+ ActivityManager mAm;
+ AppWidgetManager mAwm;
+ PackageManager mPm;
+ IPackageManager mIpm;
+ UserManager mUm;
+ SearchManager mSm;
+ WindowManager mWm;
+ Display mDisplay;
+ String mRecentsPackage;
+ ComponentName mAssistComponent;
+
+ Bitmap mDummyIcon;
+ Paint mBgProtectionPaint;
+ Canvas mBgProtectionCanvas;
+
+ /** Private constructor */
+ public SystemServicesProxy(Context context) {
+ mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ mAwm = AppWidgetManager.getInstance(context);
+ mPm = context.getPackageManager();
+ mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mIpm = AppGlobals.getPackageManager();
+ mSm = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
+ mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mDisplay = mWm.getDefaultDisplay();
+ mRecentsPackage = context.getPackageName();
+
+ // Create the protection paints
+ mBgProtectionPaint = new Paint();
+ mBgProtectionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
+ mBgProtectionPaint.setColor(0xFFffffff);
+ mBgProtectionCanvas = new Canvas();
+
+ // Resolve the assist intent
+ Intent assist = mSm.getAssistIntent(context, false);
+ if (assist != null) {
+ mAssistComponent = assist.getComponent();
+ }
+
+ if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+ // Create a dummy icon
+ mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ mDummyIcon.eraseColor(0xFF999999);
+ }
+ }
+
+ /** Returns a list of the recents tasks */
+ public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId) {
+ if (mAm == null) return null;
+
+ // If we are mocking, then create some recent tasks
+ if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+ ArrayList<ActivityManager.RecentTaskInfo> tasks =
+ new ArrayList<ActivityManager.RecentTaskInfo>();
+ int count = Math.min(numLatestTasks, Constants.DebugFlags.App.SystemServicesProxyMockTaskCount);
+ for (int i = 0; i < count; i++) {
+ // Create a dummy component name
+ int packageIndex = i % Constants.DebugFlags.App.SystemServicesProxyMockPackageCount;
+ ComponentName cn = new ComponentName("com.android.test" + packageIndex,
+ "com.android.test" + i + ".Activity");
+ String description = "" + i + " - " +
+ Long.toString(Math.abs(new Random().nextLong()), 36);
+ // Create the recent task info
+ ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
+ rti.id = rti.persistentId = i;
+ rti.baseIntent = new Intent();
+ rti.baseIntent.setComponent(cn);
+ rti.description = description;
+ rti.firstActiveTime = rti.lastActiveTime = i;
+ if (i % 2 == 0) {
+ rti.taskDescription = new ActivityManager.TaskDescription(description,
+ Bitmap.createBitmap(mDummyIcon),
+ 0xFF000000 | (0xFFFFFF & new Random().nextInt()));
+ } else {
+ rti.taskDescription = new ActivityManager.TaskDescription();
+ }
+ tasks.add(rti);
+ }
+ return tasks;
+ }
+
+ // Remove home/recents/excluded tasks
+ int minNumTasksToQuery = 10;
+ int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks);
+ List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery,
+ ActivityManager.RECENT_IGNORE_UNAVAILABLE |
+ ActivityManager.RECENT_INCLUDE_PROFILES |
+ ActivityManager.RECENT_WITH_EXCLUDED, userId);
+ boolean isFirstValidTask = true;
+ Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();
+ while (iter.hasNext()) {
+ ActivityManager.RecentTaskInfo t = iter.next();
+
+ // NOTE: The order of these checks happens in the expected order of the traversal of the
+ // tasks
+
+ // Skip tasks from this Recents package
+ if (t.baseIntent.getComponent().getPackageName().equals(mRecentsPackage)) {
+ iter.remove();
+ continue;
+ }
+ // Check the first non-recents task, include this task even if it is marked as excluded
+ // from recents. In other words, only remove excluded tasks if it is not the first task
+ boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+ == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+ if (isExcluded && !isFirstValidTask) {
+ iter.remove();
+ continue;
+ }
+ isFirstValidTask = false;
+ // Skip tasks in the home stack
+ if (isInHomeStack(t.persistentId)) {
+ iter.remove();
+ continue;
+ }
+ }
+
+ return tasks.subList(0, Math.min(tasks.size(), numLatestTasks));
+ }
+
+ /** Returns a list of the running tasks */
+ public List<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) {
+ if (mAm == null) return null;
+ return mAm.getRunningTasks(numTasks);
+ }
+
+ /** Returns whether the specified task is in the home stack */
+ public boolean isInHomeStack(int taskId) {
+ if (mAm == null) return false;
+
+ // If we are mocking, then just return false
+ if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+ return false;
+ }
+
+ return mAm.isInHomeStack(taskId);
+ }
+
+ /** Returns the top task thumbnail for the given task id */
+ public Bitmap getTaskThumbnail(int taskId) {
+ if (mAm == null) return null;
+
+ // If we are mocking, then just return a dummy thumbnail
+ if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+ Bitmap thumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ thumbnail.eraseColor(0xff333333);
+ return thumbnail;
+ }
+
+ Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId);
+ if (thumbnail != null) {
+ // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
+ // left pixel, then assume the whole thumbnail is transparent. Generally, proper
+ // screenshots are always composed onto a bitmap that has no alpha.
+ if (Color.alpha(thumbnail.getPixel(0, 0)) == 0) {
+ mBgProtectionCanvas.setBitmap(thumbnail);
+ mBgProtectionCanvas.drawRect(0, 0, thumbnail.getWidth(), thumbnail.getHeight(),
+ mBgProtectionPaint);
+ mBgProtectionCanvas.setBitmap(null);
+ Log.e(TAG, "Invalid screenshot detected from getTaskThumbnail()");
+ }
+ }
+ return thumbnail;
+ }
+
+ /**
+ * Returns a task thumbnail from the activity manager
+ */
+ public static Bitmap getThumbnail(ActivityManager activityManager, int taskId) {
+ ActivityManager.TaskThumbnail taskThumbnail = activityManager.getTaskThumbnail(taskId);
+ Bitmap thumbnail = taskThumbnail.mainThumbnail;
+ ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor;
+ if (thumbnail == null && descriptor != null) {
+ thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor());
+ }
+ if (descriptor != null) {
+ try {
+ descriptor.close();
+ } catch (IOException e) {
+ }
+ }
+ return thumbnail;
+ }
+
+ /** Moves a task to the front with the specified activity options */
+ public void moveTaskToFront(int taskId, ActivityOptions opts) {
+ if (mAm == null) return;
+ if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
+
+ if (opts != null) {
+ mAm.moveTaskToFront(taskId, ActivityManager.MOVE_TASK_WITH_HOME,
+ opts.toBundle());
+ } else {
+ mAm.moveTaskToFront(taskId, ActivityManager.MOVE_TASK_WITH_HOME);
+ }
+ }
+
+ /** Removes the task and kills the process */
+ public void removeTask(int taskId, boolean isDocument) {
+ if (mAm == null) return;
+ if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
+
+ // Remove the task, and only kill the process if it is not a document
+ mAm.removeTask(taskId, isDocument ? 0 : ActivityManager.REMOVE_TASK_KILL_PROCESS);
+ }
+
+ /**
+ * Returns the activity info for a given component name.
+ *
+ * @param cn The component name of the activity.
+ * @param userId The userId of the user that this is for.
+ */
+ public ActivityInfo getActivityInfo(ComponentName cn, int userId) {
+ if (mIpm == null) return null;
+ if (Constants.DebugFlags.App.EnableSystemServicesProxy) return new ActivityInfo();
+
+ try {
+ return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Returns the activity info for a given component name.
+ *
+ * @param cn The component name of the activity.
+ */
+ public ActivityInfo getActivityInfo(ComponentName cn) {
+ if (mPm == null) return null;
+ if (Constants.DebugFlags.App.EnableSystemServicesProxy) return new ActivityInfo();
+
+ try {
+ return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /** Returns the activity label */
+ public String getActivityLabel(ActivityInfo info) {
+ if (mPm == null) return null;
+
+ // If we are mocking, then return a mock label
+ if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+ return "Recent Task";
+ }
+
+ return info.loadLabel(mPm).toString();
+ }
+
+ /**
+ * Returns the activity icon for the ActivityInfo for a user, badging if
+ * necessary.
+ */
+ public Drawable getActivityIcon(ActivityInfo info, int userId) {
+ if (mPm == null || mUm == null) return null;
+
+ // If we are mocking, then return a mock label
+ if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
+ return new ColorDrawable(0xFF666666);
+ }
+
+ Drawable icon = info.loadIcon(mPm);
+ return getBadgedIcon(icon, userId);
+ }
+
+ /**
+ * Returns the given icon for a user, badging if necessary.
+ */
+ public Drawable getBadgedIcon(Drawable icon, int userId) {
+ if (userId != UserHandle.myUserId()) {
+ icon = mUm.getBadgedDrawableForUser(icon, new UserHandle(userId));
+ }
+ return icon;
+ }
+
+ /**
+ * Resolves and binds the search app widget that is to appear in the recents.
+ */
+ public Pair<Integer, AppWidgetProviderInfo> bindSearchAppWidget(AppWidgetHost host) {
+ if (mAwm == null) return null;
+ if (mAssistComponent == null) return null;
+
+ // Find the first Recents widget from the same package as the global assist activity
+ List<AppWidgetProviderInfo> widgets = mAwm.getInstalledProviders(
+ AppWidgetProviderInfo.WIDGET_CATEGORY_RECENTS);
+ AppWidgetProviderInfo searchWidgetInfo = null;
+ for (AppWidgetProviderInfo info : widgets) {
+ if (info.provider.getPackageName().equals(mAssistComponent.getPackageName())) {
+ searchWidgetInfo = info;
+ break;
+ }
+ }
+
+ // Return early if there is no search widget
+ if (searchWidgetInfo == null) return null;
+
+ // Allocate a new widget id and try and bind the app widget (if that fails, then just skip)
+ int searchWidgetId = host.allocateAppWidgetId();
+ Bundle opts = new Bundle();
+ opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_RECENTS);
+ if (!mAwm.bindAppWidgetIdIfAllowed(searchWidgetId, searchWidgetInfo.provider, opts)) {
+ return null;
+ }
+ return new Pair<Integer, AppWidgetProviderInfo>(searchWidgetId, searchWidgetInfo);
+ }
+
+ /**
+ * Returns the app widget info for the specified app widget id.
+ */
+ public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
+ if (mAwm == null) return null;
+
+ return mAwm.getAppWidgetInfo(appWidgetId);
+ }
+
+ /**
+ * Destroys the specified app widget.
+ */
+ public void unbindSearchAppWidget(AppWidgetHost host, int appWidgetId) {
+ if (mAwm == null) return;
+
+ // Delete the app widget
+ host.deleteAppWidgetId(appWidgetId);
+ }
+
+ /**
+ * Returns the window rect.
+ */
+ public Rect getWindowRect() {
+ Rect windowRect = new Rect();
+ if (mWm == null) return windowRect;
+
+ mWm.getDefaultDisplay().getRectSize(windowRect);
+ return windowRect;
+ }
+
+ /**
+ * Takes a screenshot of the current surface.
+ */
+ public Bitmap takeScreenshot() {
+ DisplayInfo di = new DisplayInfo();
+ mDisplay.getDisplayInfo(di);
+ return SurfaceControl.screenshot(di.getNaturalWidth(), di.getNaturalHeight());
+ }
+
+ /**
+ * Takes a screenshot of the current app.
+ */
+ public Bitmap takeAppScreenshot() {
+ return takeScreenshot();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
new file mode 100644
index 0000000..bda195b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+import android.app.ActivityManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.ParcelFileDescriptor;
+import com.android.systemui.recents.RecentsConfiguration;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/* Common code */
+public class Utilities {
+
+ // Reflection methods for altering shadows
+ private static Method sPropertyMethod;
+ static {
+ try {
+ Class<?> c = Class.forName("android.view.GLES20Canvas");
+ sPropertyMethod = c.getDeclaredMethod("setProperty", String.class, String.class);
+ if (!sPropertyMethod.isAccessible()) sPropertyMethod.setAccessible(true);
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Calculates a consistent animation duration (ms) for all animations depending on the movement
+ * of the object being animated.
+ */
+ public static int calculateTranslationAnimationDuration(int distancePx) {
+ return calculateTranslationAnimationDuration(distancePx, 100);
+ }
+ public static int calculateTranslationAnimationDuration(int distancePx, int minDuration) {
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ return Math.max(minDuration, (int) (1000f /* ms/s */ *
+ (Math.abs(distancePx) / config.animationPxMovementPerSecond)));
+ }
+
+ /** Scales a rect about its centroid */
+ public static void scaleRectAboutCenter(Rect r, float scale) {
+ if (scale != 1.0f) {
+ int cx = r.centerX();
+ int cy = r.centerY();
+ r.offset(-cx, -cy);
+ r.left = (int) (r.left * scale + 0.5f);
+ r.top = (int) (r.top * scale + 0.5f);
+ r.right = (int) (r.right * scale + 0.5f);
+ r.bottom = (int) (r.bottom * scale + 0.5f);
+ r.offset(cx, cy);
+ }
+ }
+
+ /** Calculates the luminance-preserved greyscale of a given color. */
+ public static int colorToGreyscale(int color) {
+ return Math.round(0.2126f * Color.red(color) + 0.7152f * Color.green(color) +
+ 0.0722f * Color.blue(color));
+ }
+
+ /** Returns the ideal color to draw on top of a specified background color. */
+ public static int getIdealColorForBackgroundColorGreyscale(int greyscale, int lightRes,
+ int darkRes) {
+ return (greyscale < 128) ? lightRes : darkRes;
+ }
+ /** Returns the ideal drawable to draw on top of a specified background color. */
+ public static Drawable getIdealResourceForBackgroundColorGreyscale(int greyscale,
+ Drawable lightRes,
+ Drawable darkRes) {
+ return (greyscale < 128) ? lightRes : darkRes;
+ }
+
+ /** Sets some private shadow properties. */
+ public static void setShadowProperty(String property, String value)
+ throws IllegalAccessException, InvocationTargetException {
+ sPropertyMethod.invoke(null, property, value);
+ }
+}