summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWinson Chung <winsonc@google.com>2014-03-13 17:14:17 -0700
committerWinson Chung <winsonc@google.com>2014-03-13 17:14:17 -0700
commit4d7b092a866d2fce3e11b5a12cda2b87a83af52d (patch)
treee63eb23891df33c891cd6ab35d02e9ebb8b3d70f
parente13cf5701529cd7229587b5546f4320f5b243368 (diff)
downloadframeworks_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
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Console.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Constants.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsService.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java164
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/Task.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/TaskCallbacks.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java318
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java18
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);
}