diff options
| author | Winson Chung <winsonc@google.com> | 2014-07-03 15:54:42 -0700 |
|---|---|---|
| committer | Winson Chung <winsonc@google.com> | 2014-07-07 17:33:31 -0700 |
| commit | ffa2ec664479bff6b4b61d4c349d9db2cb37ca16 (patch) | |
| tree | 512f872a6317f7f4e90083a56a796fb1dca21d5e /packages/SystemUI/src/com/android/systemui/recents/misc | |
| parent | 95f621a1a4a0891075f1f9daf8e0323aab488793 (diff) | |
| download | frameworks_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')
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); + } +} |
