diff options
author | Winson Chung <winsonc@google.com> | 2014-03-13 17:14:17 -0700 |
---|---|---|
committer | Winson Chung <winsonc@google.com> | 2014-03-13 17:14:17 -0700 |
commit | 4d7b092a866d2fce3e11b5a12cda2b87a83af52d (patch) | |
tree | e63eb23891df33c891cd6ab35d02e9ebb8b3d70f | |
parent | e13cf5701529cd7229587b5546f4320f5b243368 (diff) | |
download | frameworks_base-4d7b092a866d2fce3e11b5a12cda2b87a83af52d.zip frameworks_base-4d7b092a866d2fce3e11b5a12cda2b87a83af52d.tar.gz frameworks_base-4d7b092a866d2fce3e11b5a12cda2b87a83af52d.tar.bz2 |
Fixing memory leaks related to Tasks holding onto their callbacks.
- Switching to SwipeHelper
11 files changed, 393 insertions, 241 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Console.java b/packages/SystemUI/src/com/android/systemui/recents/Console.java index b3d9ccf..db95193 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Console.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Console.java @@ -17,6 +17,7 @@ package com.android.systemui.recents; +import android.content.ComponentCallbacks2; import android.content.Context; import android.util.Log; import android.view.MotionEvent; @@ -36,20 +37,20 @@ public class Console { /** Logs a key */ public static void log(String key) { - Console.log(true, key, "", AnsiReset); + log(true, key, "", AnsiReset); } /** Logs a conditioned key */ public static void log(boolean condition, String key) { if (condition) { - Console.log(condition, key, "", AnsiReset); + log(condition, key, "", AnsiReset); } } /** Logs a key in a specific color */ public static void log(boolean condition, String key, Object data) { if (condition) { - Console.log(condition, key, data, AnsiReset); + log(condition, key, data, AnsiReset); } } @@ -74,6 +75,50 @@ public class Console { } } + /** 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) { @@ -93,4 +138,25 @@ public class Console { 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/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index aeae4ab..57ebbc2 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -30,9 +30,11 @@ public class Constants { public static final boolean EnableTaskStackClipping = false; public static final boolean EnableBackgroundTaskLoading = true; public static final boolean ForceDisableBackgroundCache = false; + public static final boolean TaskDataLoader = false; public static final boolean SystemUIHandshake = false; public static final boolean TimeSystemCalls = false; + public static final boolean Memory = false; } public static class UI { @@ -41,7 +43,7 @@ public class Constants { public static final boolean TouchEvents = false; public static final boolean MeasureAndLayout = false; public static final boolean Clipping = false; - public static final boolean HwLayers = true; + public static final boolean HwLayers = false; } public static class TaskStack { @@ -55,13 +57,16 @@ public class Constants { public static class Values { public static class Window { + // The dark background dim is set behind the empty recents view public static final float DarkBackgroundDim = 0.5f; + // The background dim is set behind the card stack public static final float BackgroundDim = 0.35f; } public static class RecentsTaskLoader { // XXX: This should be calculated on the first load public static final int PreloadFirstTasksCount = 5; + // For debugging, this allows us to multiply the number of cards for each task public static final int TaskEntryMultiplier = 1; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index d050847..fc4d819 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -23,6 +23,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import android.widget.FrameLayout; +import com.android.systemui.recent.RecentTasksLoader; import com.android.systemui.recents.model.SpaceNode; import com.android.systemui.recents.model.TaskStack; import com.android.systemui.recents.views.RecentsView; @@ -168,6 +169,14 @@ public class RecentsActivity extends Activity { } @Override + public void onTrimMemory(int level) { + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + if (loader != null) { + loader.onTrimMemory(level); + } + } + + @Override public void onBackPressed() { if (!mRecentsView.unfilterFilteredStacks()) { super.onBackPressed(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index f3881ae..ed981ed 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -30,7 +30,6 @@ public class RecentsConfiguration { DisplayMetrics mDisplayMetrics; - public boolean layoutVerticalStack; public Rect systemInsets = new Rect(); /** Private constructor */ @@ -56,7 +55,6 @@ public class RecentsConfiguration { boolean isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; - layoutVerticalStack = isPortrait || Constants.LANDSCAPE_LAYOUT_VERTICAL_STACK; } public void updateSystemInsets(Rect insets) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java index 522ab0f..13a3424 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java @@ -57,8 +57,9 @@ public class RecentsService extends Service { tsv.computeRects(windowRect.width(), windowRect.height() - systemInsets.top); tsv.boundScroll(); TaskViewTransform transform = tsv.getStackTransform(0); + Rect taskRect = new Rect(transform.rect); - data.putParcelable("taskRect", transform.rect); + data.putParcelable("taskRect", taskRect); Message reply = Message.obtain(null, MSG_UPDATE_RECENTS_FOR_CONFIGURATION, 0, 0); reply.setData(data); msg.replyTo.send(reply); @@ -100,4 +101,12 @@ public class RecentsService extends Service { Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsService|onDestroy]"); super.onDestroy(); } + + @Override + public void onTrimMemory(int level) { + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + if (loader != null) { + loader.onTrimMemory(level); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java index c303ca7..96efed4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java @@ -17,6 +17,7 @@ package com.android.systemui.recents; import android.app.ActivityManager; +import android.content.ComponentCallbacks2; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; @@ -35,6 +36,7 @@ import com.android.systemui.recents.model.TaskStack; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; @@ -233,7 +235,7 @@ class BitmapLruCache extends LruCache<Task, Bitmap> { @Override protected int sizeOf(Task t, Bitmap bitmap) { // The cache size will be measured in kilobytes rather than number of items - return bitmap.getByteCount() / 1024; + return bitmap.getAllocationByteCount() / 1024; } } @@ -247,17 +249,28 @@ public class RecentsTaskLoader { TaskResourceLoadQueue mLoadQueue; TaskResourceLoader mLoader; + int mMaxThumbnailCacheSize; + int mMaxIconCacheSize; + BitmapDrawable mDefaultIcon; Bitmap mDefaultThumbnail; /** Private Constructor */ private RecentsTaskLoader(Context context) { + // Calculate the cache sizes int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); - int iconCacheSize = Constants.DebugFlags.App.ForceDisableBackgroundCache ? 1 : maxMemory / 16; - int thumbnailCacheSize = Constants.DebugFlags.App.ForceDisableBackgroundCache ? 1 : maxMemory / 8; - Console.log(Constants.DebugFlags.App.SystemUIHandshake, + mMaxThumbnailCacheSize = maxMemory / 8; + mMaxIconCacheSize = mMaxThumbnailCacheSize / 4; + int iconCacheSize = Constants.DebugFlags.App.ForceDisableBackgroundCache ? 1 : + mMaxIconCacheSize; + int thumbnailCacheSize = Constants.DebugFlags.App.ForceDisableBackgroundCache ? 1 : + mMaxThumbnailCacheSize; + + Console.log(Constants.DebugFlags.App.EnableBackgroundTaskLoading, "[RecentsTaskLoader|init]", "thumbnailCache: " + thumbnailCacheSize + " iconCache: " + iconCacheSize); + + // Initialize the cache and loaders mLoadQueue = new TaskResourceLoadQueue(); mIconCache = new DrawableLruCache(iconCacheSize); mThumbnailCache = new BitmapLruCache(thumbnailCacheSize); @@ -293,7 +306,7 @@ public class RecentsTaskLoader { /** Reload the set of recent tasks */ SpaceNode reload(Context context, int preloadCount) { - Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsTaskLoader|reload]"); + Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|reload]"); TaskStack stack = new TaskStack(context); SpaceNode root = new SpaceNode(context); root.setStack(stack); @@ -310,7 +323,7 @@ public class RecentsTaskLoader { Console.log(Constants.DebugFlags.App.TimeSystemCalls, "[RecentsTaskLoader|getRecentTasks]", "" + (System.currentTimeMillis() - t1) + "ms"); - Console.log(Constants.DebugFlags.App.SystemUIHandshake, + Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|tasks]", "" + tasks.size()); // Remove home/recents tasks @@ -335,35 +348,51 @@ public class RecentsTaskLoader { int taskCount = tasks.size(); for (int i = 0; i < taskCount; i++) { ActivityManager.RecentTaskInfo t = tasks.get(i); - - // Load the label, icon and thumbnail ActivityInfo info = pm.getActivityInfo(t.baseIntent.getComponent(), PackageManager.GET_META_DATA); String title = info.loadLabel(pm).toString(); - Drawable icon = null; - Bitmap thumbnail = null; Task task; - if (i >= (taskCount - preloadCount) || !Constants.DebugFlags.App.EnableBackgroundTaskLoading) { - Console.log(Constants.DebugFlags.App.SystemUIHandshake, + // Preload the specified number of apps + if (i >= (taskCount - preloadCount) || + !Constants.DebugFlags.App.EnableBackgroundTaskLoading) { + Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|preloadTask]", "i: " + i + " task: " + t.baseIntent.getComponent().getPackageName()); - icon = info.loadIcon(pm); - thumbnail = am.getTaskTopThumbnail(t.id); - for (int j = 0; j < Constants.Values.RecentsTaskLoader.TaskEntryMultiplier; j++) { - Console.log(Constants.DebugFlags.App.SystemUIHandshake, - " [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName()); - task = new Task(t.persistentId, t.baseIntent, title, icon, thumbnail); + + task = new Task(t.persistentId, t.baseIntent, title, null, null); + + // Load the icon (if possible from the cache) + if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) { + task.icon = mIconCache.get(task); + } + if (task.icon == null) { + task.icon = info.loadIcon(pm); if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) { - if (thumbnail != null) mThumbnailCache.put(task, thumbnail); - if (icon != null) { - mIconCache.put(task, icon); - } + mIconCache.put(task, task.icon); + } + } + + // Load the thumbnail (if possible from the cache) + if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) { + task.thumbnail = mThumbnailCache.get(task); + } + if (task.thumbnail == null) { + task.thumbnail = am.getTaskTopThumbnail(t.id); + if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) { + mThumbnailCache.put(task, task.thumbnail); } + } + + // Create as many tasks a we want to multiply by + for (int j = 0; j < Constants.Values.RecentsTaskLoader.TaskEntryMultiplier; j++) { + Console.log(Constants.DebugFlags.App.TaskDataLoader, + " [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName()); stack.addTask(task); } } else { + // Create as many tasks a we want to multiply by for (int j = 0; j < Constants.Values.RecentsTaskLoader.TaskEntryMultiplier; j++) { - Console.log(Constants.DebugFlags.App.SystemUIHandshake, + Console.log(Constants.DebugFlags.App.TaskDataLoader, " [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName()); task = new Task(t.persistentId, t.baseIntent, title, null, null); stack.addTask(task); @@ -388,9 +417,9 @@ public class RecentsTaskLoader { t1 = System.currentTimeMillis(); List<ActivityManager.StackInfo> stackInfos = ams.getAllStackInfos(); Console.log(Constants.DebugFlags.App.TimeSystemCalls, "[RecentsTaskLoader|getAllStackInfos]", "" + (System.currentTimeMillis() - t1) + "ms"); - Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsTaskLoader|stacks]", "" + tasks.size()); + Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|stacks]", "" + tasks.size()); for (ActivityManager.StackInfo s : stackInfos) { - Console.log(Constants.DebugFlags.App.SystemUIHandshake, " [RecentsTaskLoader|stack]", s.toString()); + Console.log(Constants.DebugFlags.App.TaskDataLoader, " [RecentsTaskLoader|stack]", s.toString()); if (stacks.containsKey(s.stackId)) { stacks.get(s.stackId).setRect(s.bounds); } @@ -403,45 +432,46 @@ public class RecentsTaskLoader { return root; } - /** Acquires the task resource data from the pool. - * XXX: Move this into Task? */ + /** Acquires the task resource data from the pool. */ public void loadTaskData(Task t) { if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) { - t.icon = mIconCache.get(t); - t.thumbnail = mThumbnailCache.get(t); + Drawable icon = mIconCache.get(t); + Bitmap thumbnail = mThumbnailCache.get(t); Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|loadTask]", - t + " icon: " + t.icon + " thumbnail: " + t.thumbnail); + t + " icon: " + icon + " thumbnail: " + thumbnail + + " thumbnailCacheSize: " + mThumbnailCache.size()); boolean requiresLoad = false; - if (t.icon == null) { - t.icon = mDefaultIcon; + if (icon == null) { + icon = mDefaultIcon; requiresLoad = true; } - if (t.thumbnail == null) { - t.thumbnail = mDefaultThumbnail; + if (thumbnail == null) { + thumbnail = mDefaultThumbnail; requiresLoad = true; } if (requiresLoad) { mLoadQueue.addTask(t); } + t.notifyTaskLoaded(thumbnail, icon); } } - /** Releases the task resource data back into the pool. - * XXX: Move this into Task? */ + /** Releases the task resource data back into the pool. */ public void unloadTaskData(Task t) { if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) { Console.log(Constants.DebugFlags.App.TaskDataLoader, - "[RecentsTaskLoader|unloadTask]", t); + "[RecentsTaskLoader|unloadTask]", t + + " thumbnailCacheSize: " + mThumbnailCache.size()); mLoadQueue.removeTask(t); - t.icon = mDefaultIcon; - t.thumbnail = mDefaultThumbnail; + t.notifyTaskUnloaded(mDefaultThumbnail, mDefaultIcon); + } else { + t.notifyTaskUnloaded(null, null); } } - /** Completely removes the resource data from the pool. - * XXX: Move this into Task? */ + /** Completely removes the resource data from the pool. */ public void deleteTaskData(Task t) { if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) { Console.log(Constants.DebugFlags.App.TaskDataLoader, @@ -449,9 +479,10 @@ public class RecentsTaskLoader { mLoadQueue.removeTask(t); mThumbnailCache.remove(t); mIconCache.remove(t); + t.notifyTaskUnloaded(mDefaultThumbnail, mDefaultIcon); + } else { + t.notifyTaskUnloaded(null, null); } - t.icon = mDefaultIcon; - t.thumbnail = mDefaultThumbnail; } /** Stops the task loader */ @@ -460,4 +491,51 @@ public class RecentsTaskLoader { mLoader.stop(); mLoadQueue.clearTasks(); } + + void onTrimMemory(int level) { + Console.log(Constants.DebugFlags.App.Memory, "[RecentsTaskLoader|onTrimMemory]", + Console.trimMemoryLevelToString(level)); + + if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) { + // If we are hidden, then we should unload each of the task keys + if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { + Console.log(Constants.DebugFlags.App.Memory, "[RecentsTaskLoader|unloadTasks]" + ); + // Unload each of the keys in the thumbnail cache + Map<Task, Bitmap> thumbnailCache = mThumbnailCache.snapshot(); + for (Task t : thumbnailCache.keySet()) { + unloadTaskData(t); + } + // As well as the keys in the icon cache + Map<Task, Drawable> iconCache = mIconCache.snapshot(); + for (Task t : iconCache.keySet()) { + unloadTaskData(t); + } + } + + switch (level) { + case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: + case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: + case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: + // We are leaving recents, so trim the data a bit + mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2); + mIconCache.trimToSize(mMaxIconCacheSize / 2); + break; + case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: + case ComponentCallbacks2.TRIM_MEMORY_MODERATE: + // We are going to be low on memory + mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4); + mIconCache.trimToSize(mMaxIconCacheSize / 4); + break; + case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: + case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: + // We are low on memory, so release everything + mThumbnailCache.evictAll(); + mIconCache.evictAll(); + break; + default: + break; + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index 9b03c5d..378984c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -54,6 +54,24 @@ public class Task { } } + /** Notifies the callback listeners that this task has been loaded */ + public void notifyTaskLoaded(Bitmap thumbnail, Drawable icon) { + this.icon = icon; + this.thumbnail = thumbnail; + if (mCb != null) { + mCb.onTaskBound(); + } + } + + /** Notifies the callback listeners that this task has been unloaded */ + public void notifyTaskUnloaded(Bitmap defaultThumbnail, Drawable defaultIcon) { + icon = defaultIcon; + thumbnail = defaultThumbnail; + if (mCb != null) { + mCb.onTaskUnbound(); + } + } + @Override public boolean equals(Object o) { // If we have multiple task entries for the same task, then we do the simple object diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskCallbacks.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskCallbacks.java index 169f56c..712580d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskCallbacks.java @@ -20,4 +20,8 @@ package com.android.systemui.recents.model; public interface TaskCallbacks { /* Notifies when a task's data has been updated */ public void onTaskDataChanged(Task task); + /* Notifies when a task has been bound */ + public void onTaskBound(); + /* Notifies when a task has been unbound */ + public void onTaskUnbound(); }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java index fe661bc..21ef9ff 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java @@ -28,6 +28,8 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.animation.LinearInterpolator; +import com.android.systemui.recents.Console; +import com.android.systemui.recents.Constants; /** * This class facilitates swipe to dismiss. It defines an interface to be implemented by the @@ -176,6 +178,9 @@ public class SwipeHelper { } public boolean onInterceptTouchEvent(MotionEvent ev) { + Console.log(Constants.DebugFlags.UI.TouchEvents, + "[SwipeHelper|interceptTouchEvent]", + Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue); final int action = ev.getAction(); switch (action) { @@ -200,7 +205,7 @@ public class SwipeHelper { if (Math.abs(delta) > mPagingTouchSlop) { mCallback.onBeginDrag(mCurrView); mDragging = true; - mInitialTouchPos = getPos(ev) - getTranslation(mCurrView); + mInitialTouchPos = pos - getTranslation(mCurrView); } } break; @@ -286,6 +291,10 @@ public class SwipeHelper { } public boolean onTouchEvent(MotionEvent ev) { + Console.log(Constants.DebugFlags.UI.TouchEvents, + "[SwipeHelper|touchEvent]", + Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue); + if (!mDragging) { if (!onInterceptTouchEvent(ev)) { return mCanCurrViewBeDimissed; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 9dd6c0b..7753d69 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -435,19 +435,12 @@ public class TaskStackView extends FrameLayout implements TaskStackCallbacks, Ta mStackRectSansPeek.top += Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height(); // Compute the task rect - if (RecentsConfiguration.getInstance().layoutVerticalStack) { - int minHeight = (int) (mStackRect.height() - - (Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height())); - int size = Math.min(minHeight, Math.min(mStackRect.width(), mStackRect.height())); - int centerX = mStackRect.centerX(); - mTaskRect.set(centerX - size / 2, mStackRectSansPeek.top, - centerX + size / 2, mStackRectSansPeek.top + size); - } else { - int size = Math.min(mStackRect.width(), mStackRect.height()); - int centerY = mStackRect.centerY(); - mTaskRect.set(mStackRectSansPeek.top, centerY - size / 2, - mStackRectSansPeek.top + size, centerY + size / 2); - } + int minHeight = (int) (mStackRect.height() - + (Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height())); + int size = Math.min(minHeight, Math.min(mStackRect.width(), mStackRect.height())); + int centerX = mStackRect.centerX(); + mTaskRect.set(centerX - size / 2, mStackRectSansPeek.top, + centerX + size / 2, mStackRectSansPeek.top + size); // Update the scroll bounds updateMinMaxScroll(false); @@ -589,7 +582,6 @@ public class TaskStackView extends FrameLayout implements TaskStackCallbacks, Ta // Report that this tasks's data is no longer being used RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); loader.unloadTaskData(task); - tv.unbindFromTask(); // Detach the view from the hierarchy detachViewFromParent(tv); @@ -610,7 +602,6 @@ public class TaskStackView extends FrameLayout implements TaskStackCallbacks, Ta // Request that this tasks's data be filled RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); loader.loadTaskData(task); - tv.syncToTask(); // Find the index where this task should be placed in the children int insertIndex = -1; @@ -678,14 +669,13 @@ public class TaskStackView extends FrameLayout implements TaskStackCallbacks, Ta } /* Handles touch events */ -class TaskStackViewTouchHandler { +class TaskStackViewTouchHandler implements SwipeHelper.Callback { static int INACTIVE_POINTER_ID = -1; TaskStackView mSv; VelocityTracker mVelocityTracker; boolean mIsScrolling; - boolean mIsSwiping; int mInitialMotionX, mInitialMotionY; int mLastMotionX, mLastMotionY; @@ -697,21 +687,24 @@ class TaskStackViewTouchHandler { int mMaximumVelocity; // The scroll touch slop is used to calculate when we start scrolling int mScrollTouchSlop; - // The swipe touch slop is used to calculate when we start swiping left/right, this takes - // precendence over the scroll touch slop in case the user makes a gesture that starts scrolling - // but is intended to be a swipe - int mSwipeTouchSlop; - // After a certain amount of scrolling, we should start ignoring checks for swiping - int mMaxScrollMotionToRejectSwipe; + // The page touch slop is used to calculate when we start swiping + float mPagingTouchSlop; + + SwipeHelper mSwipeHelper; + boolean mInterceptedBySwipeHelper; public TaskStackViewTouchHandler(Context context, TaskStackView sv) { ViewConfiguration configuration = ViewConfiguration.get(context); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mScrollTouchSlop = configuration.getScaledTouchSlop(); - mSwipeTouchSlop = 2 * mScrollTouchSlop; - mMaxScrollMotionToRejectSwipe = 4 * mScrollTouchSlop; + mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); mSv = sv; + + + float densityScale = context.getResources().getDisplayMetrics().density; + mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop); + mSwipeHelper.setMinAlpha(1f); } /** Velocity tracker helpers */ @@ -754,11 +747,18 @@ class TaskStackViewTouchHandler { "[TaskStackViewTouchHandler|interceptTouchEvent]", Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue); + // Return early if we have no children boolean hasChildren = (mSv.getChildCount() > 0); if (!hasChildren) { return false; } + // Pass through to swipe helper if we are swiping + mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev); + if (mInterceptedBySwipeHelper) { + return true; + } + boolean wasScrolling = !mSv.mScroller.isFinished() || (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning()); int action = ev.getAction(); @@ -777,7 +777,8 @@ class TaskStackViewTouchHandler { mVelocityTracker.addMovement(ev); // Check if the scroller is finished yet mIsScrolling = !mSv.mScroller.isFinished(); - mIsSwiping = false; + // Enable HW layers + mSv.addHwLayersRefCount(); break; } case MotionEvent.ACTION_MOVE: { @@ -786,25 +787,7 @@ class TaskStackViewTouchHandler { int activePointerIndex = ev.findPointerIndex(mActivePointerId); int y = (int) ev.getY(activePointerIndex); int x = (int) ev.getX(activePointerIndex); - if (mActiveTaskView != null && - mTotalScrollMotion < mMaxScrollMotionToRejectSwipe && - Math.abs(x - mInitialMotionX) > Math.abs(y - mInitialMotionY) && - Math.abs(x - mInitialMotionX) > mSwipeTouchSlop) { - // Start swiping and stop scrolling - mIsScrolling = false; - mIsSwiping = true; - System.out.println("SWIPING: " + mActiveTaskView); - // Initialize the velocity tracker if necessary - initOrResetVelocityTracker(); - mVelocityTracker.addMovement(ev); - // Disallow parents from intercepting touch events - final ViewParent parent = mSv.getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } - // Enable HW layers - mSv.addHwLayersRefCount(); - } else if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) { + if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) { // Save the touch move info mIsScrolling = true; // Initialize the velocity tracker if necessary @@ -815,8 +798,6 @@ class TaskStackViewTouchHandler { if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } - // Enable HW layers - mSv.addHwLayersRefCount(); } mLastMotionX = x; @@ -829,16 +810,17 @@ class TaskStackViewTouchHandler { mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration); // Reset the drag state and the velocity tracker mIsScrolling = false; - mIsSwiping = false; mActivePointerId = INACTIVE_POINTER_ID; mActiveTaskView = null; mTotalScrollMotion = 0; recycleVelocityTracker(); + // Disable HW layers + mSv.decHwLayersRefCount(); break; } } - return wasScrolling || mIsScrolling || mIsSwiping; + return wasScrolling || mIsScrolling; } /** Handles touch events once we have intercepted them */ @@ -853,6 +835,11 @@ class TaskStackViewTouchHandler { return false; } + // Pass through to swipe helper if we are swiping + if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) { + return true; + } + // Update the velocity tracker initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); @@ -871,7 +858,6 @@ class TaskStackViewTouchHandler { // Initialize the velocity tracker initOrResetVelocityTracker(); mVelocityTracker.addMovement(ev); - // XXX: Set mIsScrolling or mIsSwiping? // Disallow parents from intercepting touch events final ViewParent parent = mSv.getParent(); if (parent != null) { @@ -886,28 +872,7 @@ class TaskStackViewTouchHandler { int x = (int) ev.getX(activePointerIndex); int y = (int) ev.getY(activePointerIndex); int deltaY = mLastMotionY - y; - int deltaX = x - mLastMotionX; - if (!mIsSwiping) { - if (mActiveTaskView != null && - mTotalScrollMotion < mMaxScrollMotionToRejectSwipe && - Math.abs(x - mInitialMotionX) > Math.abs(y - mInitialMotionY) && - Math.abs(x - mInitialMotionX) > mSwipeTouchSlop) { - mIsScrolling = false; - mIsSwiping = true; - System.out.println("SWIPING: " + mActiveTaskView); - // Initialize the velocity tracker if necessary - initOrResetVelocityTracker(); - mVelocityTracker.addMovement(ev); - // Disallow parents from intercepting touch events - final ViewParent parent = mSv.getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } - // Enable HW layers - mSv.addHwLayersRefCount(); - } - } - if (!mIsSwiping && !mIsScrolling) { + if (!mIsScrolling) { if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) { mIsScrolling = true; // Initialize the velocity tracker @@ -927,8 +892,6 @@ class TaskStackViewTouchHandler { if (mSv.isScrollOutOfBounds()) { mVelocityTracker.clear(); } - } else if (mIsSwiping) { - mActiveTaskView.setTranslationX(mActiveTaskView.getTranslationX() + deltaX); } mLastMotionX = x; mLastMotionY = y; @@ -936,107 +899,33 @@ class TaskStackViewTouchHandler { break; } case MotionEvent.ACTION_UP: { - if (mIsScrolling || mIsSwiping) { - final TaskView activeTv = mActiveTaskView; - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - - if (mIsSwiping) { - int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId); - if ((Math.abs(initialVelocity) > mMinimumVelocity)) { - // Fling to dismiss - int newScrollX = (int) (Math.signum(initialVelocity) * - activeTv.getMeasuredWidth()); - int duration = Math.min(Constants.Values.TaskStackView.Animation.SwipeDismissDuration, - (int) (Math.abs(newScrollX - activeTv.getScrollX()) * - 1000f / Math.abs(initialVelocity))); - activeTv.animate() - .translationX(newScrollX) - .alpha(0f) - .setDuration(duration) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - Task task = activeTv.getTask(); - Activity activity = (Activity) mSv.getContext(); - - // We have to disable the listener to ensure that we - // don't hit this again - activeTv.animate().setListener(null); - - // Remove the task from the view - mSv.mStack.removeTask(task); - - // Remove any stored data from the loader - RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); - loader.deleteTaskData(task); - - // Remove the task from activity manager - final ActivityManager am = (ActivityManager) - activity.getSystemService(Context.ACTIVITY_SERVICE); - if (am != null) { - am.removeTask(activeTv.getTask().id, - ActivityManager.REMOVE_TASK_KILL_PROCESS); - } - - // If there are no remaining tasks, then just close the activity - if (mSv.mStack.getTaskCount() == 0) { - activity.finish(); - } - - // Disable HW layers - mSv.decHwLayersRefCount(); - } - }) - .start(); - // Enable HW layers - mSv.addHwLayersRefCount(); - } else { - // Animate it back into place - // XXX: Make this animation a function of the velocity OR distance - int duration = Constants.Values.TaskStackView.Animation.SwipeSnapBackDuration; - activeTv.animate() - .translationX(0) - .setDuration(duration) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Disable HW layers - mSv.decHwLayersRefCount(); - } - }) - .start(); - // Enable HW layers - mSv.addHwLayersRefCount(); - } - } else { - int velocity = (int) velocityTracker.getYVelocity(mActivePointerId); - if ((Math.abs(velocity) > mMinimumVelocity)) { - Console.log(Constants.DebugFlags.UI.TouchEvents, - "[TaskStackViewTouchHandler|fling]", - "scroll: " + mSv.getStackScroll() + " velocity: " + velocity, - Console.AnsiGreen); - // Enable HW layers on the stack - mSv.addHwLayersRefCount(); - // Fling scroll - mSv.mScroller.fling(0, mSv.getStackScroll(), - 0, -velocity, - 0, 0, - mSv.mMinScroll, mSv.mMaxScroll, - 0, 0); - // Invalidate to kick off computeScroll - mSv.invalidate(); - } else if (mSv.isScrollOutOfBounds()) { - // Animate the scroll back into bounds - // XXX: Make this animation a function of the velocity OR distance - mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration); - } - } + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int velocity = (int) velocityTracker.getYVelocity(mActivePointerId); + + if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) { + Console.log(Constants.DebugFlags.UI.TouchEvents, + "[TaskStackViewTouchHandler|fling]", + "scroll: " + mSv.getStackScroll() + " velocity: " + velocity, + Console.AnsiGreen); + // Enable HW layers on the stack + mSv.addHwLayersRefCount(); + // Fling scroll + mSv.mScroller.fling(0, mSv.getStackScroll(), + 0, -velocity, + 0, 0, + mSv.mMinScroll, mSv.mMaxScroll, + 0, 0); + // Invalidate to kick off computeScroll + mSv.invalidate(); + } else if (mSv.isScrollOutOfBounds()) { + // Animate the scroll back into bounds + // XXX: Make this animation a function of the velocity OR distance + mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration); } mActivePointerId = INACTIVE_POINTER_ID; mIsScrolling = false; - mIsSwiping = false; mTotalScrollMotion = 0; recycleVelocityTracker(); // Disable HW layers @@ -1044,25 +933,14 @@ class TaskStackViewTouchHandler { break; } case MotionEvent.ACTION_CANCEL: { - if (mIsScrolling || mIsSwiping) { - if (mIsSwiping) { - // Animate it back into place - // XXX: Make this animation a function of the velocity OR distance - int duration = Constants.Values.TaskStackView.Animation.SwipeSnapBackDuration; - mActiveTaskView.animate() - .translationX(0) - .setDuration(duration) - .start(); - } else { - // Animate the scroll back into bounds - // XXX: Make this animation a function of the velocity OR distance - mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration); - } + if (mSv.isScrollOutOfBounds()) { + // Animate the scroll back into bounds + // XXX: Make this animation a function of the velocity OR distance + mSv.animateBoundScroll(Constants.Values.TaskStackView.Animation.SnapScrollBackDuration); } mActivePointerId = INACTIVE_POINTER_ID; mIsScrolling = false; - mIsSwiping = false; mTotalScrollMotion = 0; recycleVelocityTracker(); // Disable HW layers @@ -1072,4 +950,72 @@ class TaskStackViewTouchHandler { } return true; } + + /**** SwipeHelper Implementation ****/ + + @Override + public View getChildAtPosition(MotionEvent ev) { + return findViewAtPoint((int) ev.getX(), (int) ev.getY()); + } + + @Override + public boolean canChildBeDismissed(View v) { + return true; + } + + @Override + public void onBeginDrag(View v) { + // Enable HW layers + mSv.addHwLayersRefCount(); + // Disallow parents from intercepting touch events + final ViewParent parent = mSv.getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + } + + @Override + public void onChildDismissed(View v) { + TaskView tv = (TaskView) v; + Task task = tv.getTask(); + Activity activity = (Activity) mSv.getContext(); + + // We have to disable the listener to ensure that we + // don't hit this again + tv.animate().setListener(null); + + // Remove the task from the view + mSv.mStack.removeTask(task); + + // Remove any stored data from the loader + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + loader.deleteTaskData(task); + + // Remove the task from activity manager + final ActivityManager am = (ActivityManager) + activity.getSystemService(Context.ACTIVITY_SERVICE); + if (am != null) { + am.removeTask(tv.getTask().id, + ActivityManager.REMOVE_TASK_KILL_PROCESS); + } + + // If there are no remaining tasks, then just close the activity + if (mSv.mStack.getTaskCount() == 0) { + activity.finish(); + } + + // Disable HW layers + mSv.decHwLayersRefCount(); + } + + @Override + public void onSnapBackCompleted(View v) { + // Do Nothing + } + + @Override + public void onDragCancelled(View v) { + // Disable HW layers + mSv.decHwLayersRefCount(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index b1d0d13..9ef74ca 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -255,13 +255,13 @@ public class TaskView extends FrameLayout implements View.OnClickListener, TaskC } /** Actually synchronizes the model data into the views */ - void syncToTask() { + private void syncToTask() { mThumbnailView.rebindToTask(mTask, false); mIconView.rebindToTask(mTask, false); } /** Unset the task and callback */ - void unbindFromTask() { + private void unbindFromTask() { mTask.setCallbacks(null); mThumbnailView.unbindFromTask(); mIconView.unbindFromTask(); @@ -357,16 +357,16 @@ public class TaskView extends FrameLayout implements View.OnClickListener, TaskC /** Enable the hw layers on this task view */ void enableHwLayers() { - Console.log(Constants.DebugFlags.UI.HwLayers, "[TaskView|enableHwLayers]"); mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null); } /** Disable the hw layers on this task view */ void disableHwLayers() { - Console.log(Constants.DebugFlags.UI.HwLayers, "[TaskView|disableHwLayers]"); mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null); } + /**** TaskCallbacks Implementation ****/ + @Override public void onTaskDataChanged(Task task) { Console.log(Constants.DebugFlags.App.EnableBackgroundTaskLoading, @@ -380,6 +380,16 @@ public class TaskView extends FrameLayout implements View.OnClickListener, TaskC } @Override + public void onTaskBound() { + syncToTask(); + } + + @Override + public void onTaskUnbound() { + unbindFromTask(); + } + + @Override public void onClick(View v) { mCb.onTaskIconClicked(this); } |