From 8aa9959413a06c3d2ff75e0c7be9e3cb7ac7cd2e Mon Sep 17 00:00:00 2001 From: Winson Date: Tue, 19 Jan 2016 15:07:07 -0800 Subject: [PATCH 1/4] Starting the dismiss animation in parallel with the gesture. - Introduces notion of ignored tasks for the purposes of layout in TaskStackView. This can be used during drag and drop, and while dismissing to calculate the state of the stack without the task that the user is currently interacting with. - Fixing minor layout issue when the front/back task transforms are improperly calculated when there is a single task - Fixing minor issue when the anchor task is calculated incorrectly when dismissing task views Change-Id: I1eb0864a52e53562e4d573a6ed4f8a5a1615aff9 --- .../SystemUI/res/values-sw600dp/config.xml | 2 +- packages/SystemUI/res/values/config.xml | 2 +- .../src/com/android/systemui/SwipeHelper.java | 13 +- .../history/RecentsHistoryAdapter.java | 5 +- .../systemui/recents/misc/RectFEvaluator.java | 52 ++ .../systemui/recents/model/TaskStack.java | 19 +- .../systemui/recents/views/RecentsView.java | 9 +- .../views/TaskStackLayoutAlgorithm.java | 27 +- .../systemui/recents/views/TaskStackView.java | 455 +++++++++++------- .../recents/views/TaskStackViewScroller.java | 1 + .../views/TaskStackViewTouchHandler.java | 208 +++++++- .../recents/views/TaskViewTransform.java | 29 +- 12 files changed, 609 insertions(+), 213 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index 6795da43e087a..17ff19549a0d0 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -35,7 +35,7 @@ - -4 + -3 3 - -4 + -3 3 200 + + 125 + 125 diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 47eb05a5fb5e6..91ca289d10630 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -210,7 +210,7 @@ 1dp - 64dp + 32dp 56dp diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index db55f286cf20c..3a3b19dc34411 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -183,7 +183,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD RecentsConfiguration config = Recents.getConfiguration(); RecentsActivityLaunchState launchState = config.getLaunchState(); if (!plan.hasTasks()) { - loader.preloadTasks(plan, launchState.launchedFromHome); + loader.preloadTasks(plan, -1, launchState.launchedFromHome); } RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); loadOpts.runningTaskId = launchState.launchedToTaskId; @@ -192,24 +192,14 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD loader.loadTasks(this, plan, loadOpts); TaskStack stack = plan.getTaskStack(); - ArrayList tasks = stack.getStackTasks(); - int taskCount = stack.getTaskCount(); mRecentsView.setTaskStack(stack); - // Mark the task that is the launch target - int launchTaskIndexInStack = 0; - if (launchState.launchedToTaskId != -1) { - for (int j = 0; j < taskCount; j++) { - Task t = tasks.get(j); - if (t.key.id == launchState.launchedToTaskId) { - t.isLaunchTarget = true; - launchTaskIndexInStack = tasks.size() - j - 1; - break; - } - } - } - // Animate the SystemUI scrims into view + Task launchTarget = stack.getLaunchTarget(); + int taskCount = stack.getTaskCount(); + int launchTaskIndexInStack = launchTarget != null + ? stack.indexOfStackTask(launchTarget) + : 0; boolean hasStatusBarScrim = taskCount > 0; boolean animateStatusBarScrim = launchState.launchedFromHome; boolean hasNavBarScrim = (taskCount > 0) && !config.hasTransposedNavBar; @@ -527,7 +517,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD launchOpts.loadThumbnails = false; launchOpts.onlyLoadForCache = true; RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this); - loader.preloadTasks(loadPlan, false); + loader.preloadTasks(loadPlan, -1, false); loader.loadTasks(this, loadPlan, launchOpts); EventBus.getDefault().send(new TaskStackUpdatedEvent(loadPlan.getTaskStack())); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java index 3151fd7f3e6fe..386696763605f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java @@ -37,6 +37,8 @@ public class RecentsDebugFlags implements TunerService.Tunable { public static final boolean EnableSearchBar = false; // This disables the bitmap and icon caches public static final boolean DisableBackgroundCache = false; + // Enables the task affiliations + public static final boolean EnableAffiliatedTaskGroups = true; // Enables the simulated task affiliations public static final boolean EnableSimulatedTaskGroups = false; // Defines the number of mock task affiliations per group diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 7c25d24326a98..3eee08766c735 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -118,7 +118,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements // Load the next task only if we aren't svelte RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); - loader.preloadTasks(plan, true); + loader.preloadTasks(plan, -1, true /* isTopTaskHome */); RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); // This callback is made when a new activity is launched and the old one is paused // so ignore the current activity and try and preload the thumbnail for the @@ -198,7 +198,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements // We can use a new plan since the caches will be the same. RecentsTaskLoader loader = Recents.getTaskLoader(); RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); - loader.preloadTasks(plan, true /* isTopTaskHome */); + loader.preloadTasks(plan, -1, true /* isTopTaskHome */); RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); launchOpts.numVisibleTasks = loader.getIconCacheSize(); launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize(); @@ -370,7 +370,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements sInstanceLoadPlan = loader.createLoadPlan(mContext); if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) { sInstanceLoadPlan.preloadRawTasks(topTaskHome.value); - loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value); + loader.preloadTasks(sInstanceLoadPlan, topTask.id, topTaskHome.value); TaskStack stack = sInstanceLoadPlan.getTaskStack(); if (stack.getTaskCount() > 0) { // We try and draw the thumbnail transition bitmap in parallel before @@ -399,7 +399,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements SystemServicesProxy ssp = Recents.getSystemServices(); RecentsTaskLoader loader = Recents.getTaskLoader(); RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); - loader.preloadTasks(plan, true /* isTopTaskHome */); + loader.preloadTasks(plan, -1, true /* isTopTaskHome */); TaskStack focusedStack = plan.getTaskStack(); // Return early if there are no tasks in the focused stack @@ -451,7 +451,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements SystemServicesProxy ssp = Recents.getSystemServices(); RecentsTaskLoader loader = Recents.getTaskLoader(); RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); - loader.preloadTasks(plan, true /* isTopTaskHome */); + loader.preloadTasks(plan, -1, true /* isTopTaskHome */); TaskStack focusedStack = plan.getTaskStack(); // Return early if there are no tasks in the focused stack @@ -756,29 +756,18 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView, int runningTaskId, Task runningTaskOut) { // Find the running task in the TaskStack - Task task = null; - ArrayList tasks = stack.getStackTasks(); - if (runningTaskId != -1) { - // Otherwise, try and find the task with the - int taskCount = tasks.size(); - for (int i = taskCount - 1; i >= 0; i--) { - Task t = tasks.get(i); - if (t.key.id == runningTaskId) { - task = t; - runningTaskOut.copyFrom(t); - break; - } - } - } - if (task == null) { + Task launchTask = stack.getLaunchTarget(); + if (launchTask != null) { + runningTaskOut.copyFrom(launchTask); + } else { // If no task is specified or we can not find the task just use the front most one - task = tasks.get(tasks.size() - 1); - runningTaskOut.copyFrom(task); + launchTask = stack.getStackFrontMostTask(); + runningTaskOut.copyFrom(launchTask); } // Get the transform for the running task stackView.getScroller().setStackScrollToInitialState(); - mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task, + mTmpTransform = stackView.getStackAlgorithm().getStackTransform(launchTask, stackView.getScroller().getStackScroll(), mTmpTransform, null); return mTmpTransform; } @@ -826,7 +815,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements sInstanceLoadPlan = loader.createLoadPlan(mContext); } if (mReloadTasks || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) { - loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome); + loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome); } TaskStack stack = sInstanceLoadPlan.getTaskStack(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java index 822ad77e71b81..4bc42ea823538 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -25,7 +25,8 @@ import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.os.UserManager; import android.util.ArraySet; - +import android.util.SparseArray; +import android.util.SparseIntArray; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.recents.Recents; @@ -92,7 +93,7 @@ public class RecentsTaskLoadPlan { } /** - * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent + * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent * to most-recent order. */ public synchronized void preloadRawTasks(boolean isTopTaskHome) { @@ -107,7 +108,7 @@ public class RecentsTaskLoadPlan { } /** - * Preloads the list of recent tasks from the system. After this call, the TaskStack will + * Preloads the list of recent tasks from the system. After this call, the TaskStack will * have a list of all the recent tasks with their metadata, not including icons or * thumbnails which were not cached and have to be loaded. * @@ -115,13 +116,16 @@ public class RecentsTaskLoadPlan { * - least-recent to most-recent stack tasks * - least-recent to most-recent freeform tasks */ - public synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) { + public synchronized void preloadPlan(RecentsTaskLoader loader, int topTaskId, + boolean isTopTaskHome) { Resources res = mContext.getResources(); ArrayList allTasks = new ArrayList<>(); if (mRawTasks == null) { preloadRawTasks(isTopTaskHome); } + SparseArray affiliatedTasks = new SparseArray<>(); + SparseIntArray affiliatedTaskCounts = new SparseIntArray(); String dismissDescFormat = mContext.getString( R.string.accessibility_recents_item_will_be_dismissed); long lastStackActiveTime = Prefs.getLong(mContext, @@ -131,6 +135,17 @@ public class RecentsTaskLoadPlan { for (int i = 0; i < taskCount; i++) { ActivityManager.RecentTaskInfo t = mRawTasks.get(i); + // Affiliated tasks are returned in a specific order from ActivityManager but without a + // lastActiveTime since it hasn't yet been started. However, we later sort the task list + // by lastActiveTime, which rearranges the tasks. For now, we need to workaround this + // by updating the lastActiveTime of this task to the lastActiveTime of the task it is + // affiliated with, in the same order that we encounter it in the original list (just + // its index in the task group for the task it is affiliated with). + if (t.persistentId != t.affiliatedTaskId) { + t.lastActiveTime = affiliatedTasks.get(t.affiliatedTaskId).lastActiveTime + + affiliatedTaskCounts.get(t.affiliatedTaskId, 0) + 1; + } + // Compose the task key Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent, t.userId, t.firstActiveTime, t.lastActiveTime); @@ -140,6 +155,7 @@ public class RecentsTaskLoadPlan { boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId); boolean isStackTask = isFreeformTask || (!isHistoricalTask(t) || (t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS))); + boolean isLaunchTarget = taskKey.id == topTaskId; if (isStackTask && newLastStackActiveTime < 0) { newLastStackActiveTime = t.lastActiveTime; } @@ -157,9 +173,11 @@ public class RecentsTaskLoadPlan { // Add the task to the stack Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon, thumbnail, title, contentDescription, dismissDescription, activityColor, - !isStackTask, t.bounds, t.taskDescription); + !isStackTask, isLaunchTarget, t.bounds, t.taskDescription); allTasks.add(task); + affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1); + affiliatedTasks.put(taskKey.id, taskKey); } if (newLastStackActiveTime != -1) { Prefs.putLong(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java index 28338d8330fa4..8d8fec632ef9b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java @@ -314,8 +314,8 @@ public class RecentsTaskLoader { } /** Preloads recents tasks using the specified plan to store the output. */ - public void preloadTasks(RecentsTaskLoadPlan plan, boolean isTopTaskHome) { - plan.preloadPlan(this, isTopTaskHome); + public void preloadTasks(RecentsTaskLoadPlan plan, int topTaskId, boolean isTopTaskHome) { + plan.preloadPlan(this, topTaskId, isTopTaskHome); } /** Begins loading the heavy task data according to the specified options. */ 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 29e7077ccf76d..193bd17b81e7d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -154,7 +154,8 @@ public class Task { public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon, Bitmap thumbnail, String title, String contentDescription, String dismissDescription, int colorPrimary, boolean isHistorical, - Rect bounds, ActivityManager.TaskDescription taskDescription) { + boolean isLaunchTarget, Rect bounds, + ActivityManager.TaskDescription taskDescription) { boolean isInAffiliationGroup = (affiliationTaskId != key.id); boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0); this.key = key; @@ -170,6 +171,7 @@ public class Task { Color.WHITE) > 3f; this.bounds = bounds; this.taskDescription = taskDescription; + this.isLaunchTarget = isLaunchTarget; this.isHistorical = isHistorical; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index ad723e23e671e..c73273e6f2588 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -848,12 +848,17 @@ public class TaskStack { for (int i = 0; i < taskCount; i++) { Task t = tasks.get(i); TaskGrouping group; - int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId : - IndividualTaskIdOffset + t.key.id; - if (mAffinitiesGroups.containsKey(affiliation)) { - group = getGroupWithAffiliation(affiliation); + if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) { + int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId : + IndividualTaskIdOffset + t.key.id; + if (mAffinitiesGroups.containsKey(affiliation)) { + group = getGroupWithAffiliation(affiliation); + } else { + group = new TaskGrouping(affiliation); + addGroup(group); + } } else { - group = new TaskGrouping(affiliation); + group = new TaskGrouping(t.key.id); addGroup(group); } group.addTask(t); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java index 2930f4d5c08f6..ccbb329bb151b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java @@ -130,6 +130,7 @@ public class TaskStackAnimationHelper { // Move the task view slightly lower so we can animate it in RectF bounds = new RectF(mTmpTransform.rect); bounds.offset(0, taskViewAffiliateGroupEnterOffset); + tv.setClipViewInStack(false); tv.setAlpha(0f); tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom); @@ -165,6 +166,8 @@ public class TaskStackAnimationHelper { int taskViewEnterFromAppDuration = res.getInteger( R.integer.recents_task_enter_from_app_duration); + int taskViewEnterFromAffiliatedAppDuration = res.getInteger( + R.integer.recents_task_enter_from_affiliated_app_duration); int taskViewEnterFromHomeDuration = res.getInteger( R.integer.recents_task_enter_from_home_duration); int taskViewEnterFromHomeStaggerDelay = res.getInteger( @@ -174,7 +177,7 @@ public class TaskStackAnimationHelper { List taskViews = mStackView.getTaskViews(); int taskViewCount = taskViews.size(); for (int i = taskViewCount - 1; i >= 0; i--) { - TaskView tv = taskViews.get(i); + final TaskView tv = taskViews.get(i); Task task = tv.getTask(); boolean currentTaskOccludesLaunchTarget = false; if (launchTargetTask != null) { @@ -195,8 +198,14 @@ public class TaskStackAnimationHelper { // Animate the task up if it was occluding the launch target if (currentTaskOccludesLaunchTarget) { TaskViewAnimation taskAnimation = new TaskViewAnimation( - taskViewEnterFromAppDuration, PhoneStatusBar.ALPHA_IN, - postAnimationTrigger.decrementOnAnimationEnd()); + taskViewEnterFromAffiliatedAppDuration, PhoneStatusBar.ALPHA_IN, + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + postAnimationTrigger.decrement(); + tv.setClipViewInStack(false); + } + }); postAnimationTrigger.increment(); mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); } @@ -286,7 +295,7 @@ public class TaskStackAnimationHelper { } else if (currentTaskOccludesLaunchTarget) { // Animate this task out of view TaskViewAnimation taskAnimation = new TaskViewAnimation( - taskViewExitToAppDuration, mFastOutLinearInInterpolator, + taskViewExitToAppDuration, PhoneStatusBar.ALPHA_OUT, postAnimationTrigger.decrementOnAnimationEnd()); postAnimationTrigger.increment(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index 5bd5a8159803a..e99509cb83650 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -434,19 +434,25 @@ public class TaskStackLayoutAlgorithm { mFreeformLayoutAlgorithm.update(freeformTasks, this); mInitialScrollP = mMaxScrollP; } else { + Task launchTask = stack.getLaunchTarget(); + int launchTaskIndex = launchTask != null + ? stack.indexOfStackTask(launchTask) + : mNumStackTasks - 1; if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) { mInitialScrollP = mMinScrollP; } else if (getDefaultFocusState() > 0f) { RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); if (launchState.launchedFromHome) { - mInitialScrollP = Math.max(mMinScrollP, mNumStackTasks - 1); + mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, launchTaskIndex)); } else { - mInitialScrollP = Math.max(mMinScrollP, mNumStackTasks - 2); + mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, + launchTaskIndex - 1)); } } else { float offsetPct = (float) (mTaskRect.height() / 2) / mStackRect.height(); float normX = mUnfocusedCurveInterpolator.getX(offsetPct); - mInitialScrollP = (mNumStackTasks - 1) - mUnfocusedRange.getAbsoluteX(normX); + mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, + launchTaskIndex - mUnfocusedRange.getAbsoluteX(normX))); } } } 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 3b78d71c3a5c2..de472bcd59a62 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -1220,9 +1220,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Set the task focused state without requesting view focus, and leave the focus animations // until after the enter-animation + Task launchTask = mStack.getLaunchTarget(); RecentsConfiguration config = Recents.getConfiguration(); RecentsActivityLaunchState launchState = config.getLaunchState(); - int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount()); + int focusedTaskIndex = launchTask != null + ? mStack.indexOfStackTask(launchTask) + : launchState.getInitialFocusTaskIndex(mStack.getTaskCount()); if (focusedTaskIndex != -1) { setFocusedTask(focusedTaskIndex, false /* scrollToTask */, false /* requestViewFocus */); 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 9b727020404f7..ba0fc8f017690 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -459,16 +459,14 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, public void showActionButton(boolean fadeIn, int fadeInDuration) { mActionButtonView.setVisibility(View.VISIBLE); - if (fadeIn) { - if (mActionButtonView.getAlpha() < 1f) { - mActionButtonView.animate() - .alpha(1f) - .scaleX(1f) - .scaleY(1f) - .setDuration(fadeInDuration) - .setInterpolator(PhoneStatusBar.ALPHA_IN) - .start(); - } + if (fadeIn && mActionButtonView.getAlpha() < 1f) { + mActionButtonView.animate() + .alpha(1f) + .scaleX(1f) + .scaleY(1f) + .setDuration(fadeInDuration) + .setInterpolator(PhoneStatusBar.ALPHA_IN) + .start(); } else { mActionButtonView.setScaleX(1f); mActionButtonView.setScaleY(1f); @@ -484,29 +482,27 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, */ public void hideActionButton(boolean fadeOut, int fadeOutDuration, boolean scaleDown, final Animator.AnimatorListener animListener) { - if (fadeOut) { - if (mActionButtonView.getAlpha() > 0f) { - if (scaleDown) { - float toScale = 0.9f; - mActionButtonView.animate() - .scaleX(toScale) - .scaleY(toScale); - } + if (fadeOut && mActionButtonView.getAlpha() > 0f) { + if (scaleDown) { + float toScale = 0.9f; mActionButtonView.animate() - .alpha(0f) - .setDuration(fadeOutDuration) - .setInterpolator(PhoneStatusBar.ALPHA_OUT) - .withEndAction(new Runnable() { - @Override - public void run() { - if (animListener != null) { - animListener.onAnimationEnd(null); - } - mActionButtonView.setVisibility(View.INVISIBLE); - } - }) - .start(); + .scaleX(toScale) + .scaleY(toScale); } + mActionButtonView.animate() + .alpha(0f) + .setDuration(fadeOutDuration) + .setInterpolator(PhoneStatusBar.ALPHA_OUT) + .withEndAction(new Runnable() { + @Override + public void run() { + if (animListener != null) { + animListener.onAnimationEnd(null); + } + mActionButtonView.setVisibility(View.INVISIBLE); + } + }) + .start(); } else { mActionButtonView.setAlpha(0f); mActionButtonView.setVisibility(View.INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index e7717ac1c871a..15cb14a15c4b7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -228,6 +228,7 @@ public class TaskViewHeader extends FrameLayout mTaskViewRect.set(0, 0, width, height); boolean updateMoveTaskButton = mMoveTaskButton.getVisibility() != View.GONE; + boolean isFreeformTask = (mTask != null) && mTask.isFreeformTask(); int appIconWidth = mIconView.getMeasuredWidth(); int activityDescWidth = (mTask != null) ? (int) mTitleView.getPaint().measureText(mTask.title) @@ -239,19 +240,20 @@ public class TaskViewHeader extends FrameLayout // Priority-wise, we show the activity icon first, the dismiss icon if there is room, the // move-task icon if there is room, and then finally, the activity label if there is room - if (width < (appIconWidth + dismissIconWidth)) { + if (isFreeformTask && width < (appIconWidth + dismissIconWidth)) { mTitleView.setVisibility(View.INVISIBLE); if (updateMoveTaskButton) { mMoveTaskButton.setVisibility(View.INVISIBLE); } mDismissButton.setVisibility(View.INVISIBLE); - } else if (width < (appIconWidth + dismissIconWidth + moveTaskIconWidth)) { + } else if (isFreeformTask && width < (appIconWidth + dismissIconWidth + + moveTaskIconWidth)) { mTitleView.setVisibility(View.INVISIBLE); if (updateMoveTaskButton) { mMoveTaskButton.setVisibility(View.INVISIBLE); } mDismissButton.setVisibility(View.VISIBLE); - } else if (width < (appIconWidth + dismissIconWidth + moveTaskIconWidth + + } else if (isFreeformTask && width < (appIconWidth + dismissIconWidth + moveTaskIconWidth + activityDescWidth)) { mTitleView.setVisibility(View.INVISIBLE); if (updateMoveTaskButton) { From b433c5be013803ca742565c018de0cf456828cd7 Mon Sep 17 00:00:00 2001 From: Winson Date: Wed, 20 Jan 2016 17:11:29 -0800 Subject: [PATCH 3/4] Fixing issue with multiple focus timer animations running. Change-Id: I7243f30b917bb311e46efa1e09b44440a0236f07 --- .../src/com/android/systemui/recents/views/TaskStackView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 de472bcd59a62..a314ae2f90213 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -814,8 +814,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Reset the last focused task state if changed if (mFocusedTask != null) { - resetFocusedTask(mFocusedTask); - // Cancel the timer indicator, if applicable if (showTimerIndicator) { final TaskView tv = getChildViewForTask(mFocusedTask); @@ -823,6 +821,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal tv.getHeaderView().cancelFocusTimerIndicator(); } } + + resetFocusedTask(mFocusedTask); } boolean willScroll = false; From c5ef63f4e95329158d88a6cbb05789b57098222d Mon Sep 17 00:00:00 2001 From: Winson Date: Thu, 21 Jan 2016 14:39:23 -0800 Subject: [PATCH 4/4] Adding reveal transition to show app-overlay. - Instead of long-pressing the icon to launch into app info directly, this will mirror notifications and reveal a space that can show the app name and a link to app info. Change-Id: I91c5839719b030a65744f5e677a436e9c719c578 --- .../res/drawable/recents_info_dark.xml | 24 +++ .../res/drawable/recents_info_light.xml | 24 +++ .../res/layout/recents_task_view_header.xml | 23 ++- .../recents_task_view_header_overlay.xml | 53 ++++++ .../recents_task_view_header_progress_bar.xml | 22 +++ .../history/RecentsHistoryAdapter.java | 1 - .../recents/misc/SystemServicesProxy.java | 53 ++++-- .../recents/model/RecentsTaskLoadPlan.java | 2 +- .../recents/model/RecentsTaskLoader.java | 15 +- .../systemui/recents/views/TaskStackView.java | 5 +- .../systemui/recents/views/TaskView.java | 1 + .../recents/views/TaskViewHeader.java | 176 +++++++++++++++--- 12 files changed, 332 insertions(+), 67 deletions(-) create mode 100644 packages/SystemUI/res/drawable/recents_info_dark.xml create mode 100644 packages/SystemUI/res/drawable/recents_info_light.xml create mode 100644 packages/SystemUI/res/layout/recents_task_view_header_overlay.xml create mode 100644 packages/SystemUI/res/layout/recents_task_view_header_progress_bar.xml diff --git a/packages/SystemUI/res/drawable/recents_info_dark.xml b/packages/SystemUI/res/drawable/recents_info_dark.xml new file mode 100644 index 0000000000000..b1a22425ef2d8 --- /dev/null +++ b/packages/SystemUI/res/drawable/recents_info_dark.xml @@ -0,0 +1,24 @@ + + + + diff --git a/packages/SystemUI/res/drawable/recents_info_light.xml b/packages/SystemUI/res/drawable/recents_info_light.xml new file mode 100644 index 0000000000000..bc58c3b406d3f --- /dev/null +++ b/packages/SystemUI/res/drawable/recents_info_light.xml @@ -0,0 +1,24 @@ + + + + diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml index 07ac39a3eceae..a94e781d4a8d0 100644 --- a/packages/SystemUI/res/layout/recents_task_view_header.xml +++ b/packages/SystemUI/res/layout/recents_task_view_header.xml @@ -64,12 +64,19 @@ android:background="@drawable/recents_button_bg" android:visibility="invisible" android:src="@drawable/recents_dismiss_light" /> - + + + + + + diff --git a/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml b/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml new file mode 100644 index 0000000000000..5cdde9e396d0c --- /dev/null +++ b/packages/SystemUI/res/layout/recents_task_view_header_overlay.xml @@ -0,0 +1,53 @@ + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/recents_task_view_header_progress_bar.xml b/packages/SystemUI/res/layout/recents_task_view_header_progress_bar.xml new file mode 100644 index 0000000000000..e740458728dd7 --- /dev/null +++ b/packages/SystemUI/res/layout/recents_task_view_header_progress_bar.xml @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java index fe23fa07c1e68..f6655c7f86a2a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java @@ -144,7 +144,6 @@ public class RecentsHistoryAdapter extends RecyclerView.Adapter mTasks = new ArrayList<>(); private final List mRows = new ArrayList<>(); private final SparseIntArray mTaskRowCount = new SparseIntArray(); private TaskStack mStack; diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index adc1e9200daa4..3f52ae8d7ed15 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -30,6 +30,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -554,39 +555,41 @@ public class SystemServicesProxy { } } - /** Returns the activity label */ - public String getActivityLabel(ActivityInfo info) { + /** + * Returns the activity label, badging if necessary. + */ + public String getBadgedActivityLabel(ActivityInfo info, int userId) { if (mPm == null) return null; // If we are mocking, then return a mock label if (RecentsDebugFlags.Static.EnableSystemServicesProxy) { - return "Recent Task"; + return "Recent Task: " + userId; } - return info.loadLabel(mPm).toString(); + return getBadgedLabel(info.loadLabel(mPm).toString(), userId); } - /** Returns the application label */ - public String getApplicationLabel(Intent baseIntent, int userId) { + /** + * Returns the application label, badging if necessary. + */ + public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) { if (mPm == null) return null; // If we are mocking, then return a mock label if (RecentsDebugFlags.Static.EnableSystemServicesProxy) { - return "Recent Task"; + return "Recent Task App: " + userId; } - ResolveInfo ri = mPm.resolveActivityAsUser(baseIntent, 0, userId); - CharSequence label = (ri != null) ? ri.loadLabel(mPm) : null; - return (label != null) ? label.toString() : null; + return getBadgedLabel(appInfo.loadLabel(mPm).toString(), userId); } - /** Returns the content description for a given task */ - public String getContentDescription(Intent baseIntent, int userId, String activityLabel, - Resources res) { - String applicationLabel = getApplicationLabel(baseIntent, userId); - if (applicationLabel == null) { - return getBadgedLabel(activityLabel, userId); - } + /** + * Returns the content description for a given task, badging it if necessary. The content + * description joins the app and activity labels. + */ + public String getBadgedContentDescription(ActivityInfo info, int userId, Resources res) { + String activityLabel = info.loadLabel(mPm).toString(); + String applicationLabel = info.applicationInfo.loadLabel(mPm).toString(); String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId); return applicationLabel.equals(activityLabel) ? badgedApplicationLabel : res.getString(R.string.accessibility_recents_task_header, @@ -609,6 +612,22 @@ public class SystemServicesProxy { return getBadgedIcon(icon, userId); } + /** + * Returns the application icon for the ApplicationInfo for a user, badging if + * necessary. + */ + public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) { + if (mPm == null) return null; + + // If we are mocking, then return a mock label + if (RecentsDebugFlags.Static.EnableSystemServicesProxy) { + return new ColorDrawable(0xFF666666); + } + + Drawable icon = appInfo.loadIcon(mPm); + return getBadgedIcon(icon, userId); + } + /** * Returns the task description icon, loading and badging it if it necessary. */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java index 4bc42ea823538..9cdd703852fe3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -162,7 +162,7 @@ public class RecentsTaskLoadPlan { // Load the title, icon, and color String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription); - String contentDescription = loader.getAndUpdateContentDescription(taskKey, title, res); + String contentDescription = loader.getAndUpdateContentDescription(taskKey, res); String dismissDescription = String.format(dismissDescFormat, contentDescription); Drawable icon = isStackTask ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false) diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java index 8d8fec632ef9b..44ad2397bd655 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java @@ -436,7 +436,7 @@ public class RecentsTaskLoader { // All short paths failed, load the label from the activity info and cache it ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); if (activityInfo != null) { - label = ssp.getActivityLabel(activityInfo); + label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId); mActivityLabelCache.put(taskKey, label); return label; } @@ -449,8 +449,7 @@ public class RecentsTaskLoader { * Returns the cached task content description if the task key is not expired, updating the * cache if it is. */ - String getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel, - Resources res) { + String getAndUpdateContentDescription(Task.TaskKey taskKey, Resources res) { SystemServicesProxy ssp = Recents.getSystemServices(); // Return the cached content description if it exists @@ -458,13 +457,11 @@ public class RecentsTaskLoader { if (label != null) { return label; } - // If the given activity label is empty, don't compute or cache the content description - if (activityLabel.isEmpty()) { - return ""; - } - label = ssp.getContentDescription(taskKey.baseIntent, taskKey.userId, activityLabel, res); - if (label != null) { + // All short paths failed, load the label from the activity info and cache it + ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); + if (activityInfo != null) { + label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, res); mContentDescriptionCache.put(taskKey, label); return label; } 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 a314ae2f90213..809d4eed7a116 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -1549,7 +1549,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Poke the doze trigger on user interaction mUIDozeTrigger.poke(); if (event.showTimerIndicator && mFocusedTask != null) { - getChildViewForTask(mFocusedTask).getHeaderView().cancelFocusTimerIndicator(); + TaskView tv = getChildViewForTask(mFocusedTask); + if (tv != null) { + tv.getHeaderView().cancelFocusTimerIndicator(); + } } } 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 ba0fc8f017690..1f8216ffd750d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -162,6 +162,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, /** Resets this TaskView for reuse. */ void reset() { + mHeaderView.reset(); resetViewProperties(); resetNoUserInteractionState(); setClipViewInStack(false); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index 15cb14a15c4b7..2563190047f22 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -16,22 +16,27 @@ package com.android.systemui.recents.views; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.annotation.Nullable; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.ActivityInfo; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; -import android.graphics.PorterDuff; import android.graphics.PixelFormat; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.graphics.drawable.RippleDrawable; -import android.support.v4.graphics.ColorUtils; import android.os.CountDownTimer; +import android.support.v4.graphics.ColorUtils; import android.util.AttributeSet; import android.view.View; +import android.view.ViewAnimationUtils; +import android.view.ViewStub; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.FrameLayout; @@ -39,10 +44,10 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import com.android.internal.logging.MetricsLogger; - import com.android.systemui.R; import com.android.systemui.recents.Constants; import com.android.systemui.recents.Recents; +import com.android.systemui.recents.RecentsDebugFlags; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.LaunchTaskEvent; import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; @@ -59,6 +64,8 @@ public class TaskViewHeader extends FrameLayout implements View.OnClickListener, View.OnLongClickListener { private static final float HIGHLIGHT_LIGHTNESS_INCREMENT = 0.125f; + private static final float OVERLAY_LIGHTNESS_INCREMENT = -0.0625f; + private static final int OVERLAY_REVEAL_DURATION = 250; private static final long FOCUS_INDICATOR_INTERVAL_MS = 30; /** @@ -69,8 +76,6 @@ public class TaskViewHeader extends FrameLayout private Paint mHighlightPaint = new Paint(); private Paint mBackgroundPaint = new Paint(); - private float[] mTmpHSL = new float[3]; - public HighlightColorDrawable() { mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0)); mBackgroundPaint.setAntiAlias(true); @@ -122,11 +127,16 @@ public class TaskViewHeader extends FrameLayout Task mTask; // Header views - ImageView mMoveTaskButton; - ImageView mDismissButton; ImageView mIconView; TextView mTitleView; - int mMoveTaskTargetStackId = INVALID_STACK_ID; + ImageView mMoveTaskButton; + ImageView mDismissButton; + ViewStub mAppOverlayViewStub; + FrameLayout mAppOverlayView; + ImageView mAppIconView; + ImageView mAppInfoView; + TextView mAppTitleView; + ViewStub mFocusTimerIndicatorStub; ProgressBar mFocusTimerIndicator; // Header drawables @@ -140,21 +150,26 @@ public class TaskViewHeader extends FrameLayout Drawable mDarkFreeformIcon; Drawable mLightFullscreenIcon; Drawable mDarkFullscreenIcon; + Drawable mLightInfoIcon; + Drawable mDarkInfoIcon; int mTaskBarViewLightTextColor; int mTaskBarViewDarkTextColor; + int mMoveTaskTargetStackId = INVALID_STACK_ID; // Header background private HighlightColorDrawable mBackground; + private HighlightColorDrawable mOverlayBackground; + private float[] mTmpHSL = new float[3]; // Header dim, which is only used when task view hardware layers are not used private Paint mDimLayerPaint = new Paint(); Interpolator mFastOutSlowInInterpolator; Interpolator mFastOutLinearInInterpolator; + Interpolator mLinearOutSlowInInterpolator; - long mFocusIndicatorProgress; private CountDownTimer mFocusTimerCountDown; - long mFocusTimerDuration; + private long mFocusTimerDuration; public TaskViewHeader(Context context) { this(context, null); @@ -184,36 +199,45 @@ public class TaskViewHeader extends FrameLayout mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark); mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light); mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark); + mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light); + mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.fast_out_slow_in); mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.fast_out_linear_in); + mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.linear_out_slow_in); // Configure the background and dim mBackground = new HighlightColorDrawable(); mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f); setBackground(mBackground); + mOverlayBackground = new HighlightColorDrawable(); mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0)); mDimLayerPaint.setAntiAlias(true); mFocusTimerDuration = res.getInteger(R.integer.recents_auto_advance_duration); } + /** + * Resets this header along with the TaskView. + */ + public void reset() { + hideAppOverlay(true /* immediate */); + } + @Override protected void onFinishInflate() { // Initialize the icon and description views mIconView = (ImageView) findViewById(R.id.icon); + mIconView.setClickable(false); mIconView.setOnLongClickListener(this); mTitleView = (TextView) findViewById(R.id.title); mDismissButton = (ImageView) findViewById(R.id.dismiss_task); mDismissButton.setOnClickListener(this); mMoveTaskButton = (ImageView) findViewById(R.id.move_task); - mFocusTimerIndicator = (ProgressBar) findViewById(R.id.focus_timer_indicator); - - // Hide the backgrounds if they are ripple drawables - if (mIconView.getBackground() instanceof RippleDrawable) { - mIconView.setBackground(null); - } + mFocusTimerIndicatorStub = (ViewStub) findViewById(R.id.focus_timer_indicator_stub); + mAppOverlayViewStub = (ViewStub) findViewById(R.id.app_overlay_stub); } /** @@ -274,11 +298,6 @@ public class TaskViewHeader extends FrameLayout invalidate(); } - @Override - protected boolean verifyDrawable(Drawable who) { - return super.verifyDrawable(who) || (who == mBackground); - } - @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); @@ -290,6 +309,10 @@ public class TaskViewHeader extends FrameLayout /** Starts the focus timer. */ public void startFocusTimerIndicator() { + if (mFocusTimerIndicator == null) { + return; + } + mFocusTimerIndicator.setVisibility(View.VISIBLE); mFocusTimerIndicator.setMax((int) mFocusTimerDuration); if (mFocusTimerCountDown == null) { @@ -310,7 +333,11 @@ public class TaskViewHeader extends FrameLayout /** Cancels the focus timer. */ public void cancelFocusTimerIndicator() { - if (mFocusTimerCountDown != null && mFocusTimerIndicator != null) { + if (mFocusTimerIndicator == null) { + return; + } + + if (mFocusTimerCountDown != null) { mFocusTimerCountDown.cancel(); mFocusTimerIndicator.setProgress(0); mFocusTimerIndicator.setVisibility(View.INVISIBLE); @@ -339,6 +366,10 @@ public class TaskViewHeader extends FrameLayout private void updateBackgroundColor(float dimAlpha) { if (mTask != null) { mBackground.setColorAndDim(mTask.colorPrimary, dimAlpha); + // TODO: Consider using the saturation of the color to adjust the lightness as well + ColorUtils.colorToHSL(mTask.colorPrimary, mTmpHSL); + mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha)); + mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha); mDimLayerPaint.setAlpha((int) (dimAlpha * 255)); } } @@ -384,10 +415,15 @@ public class TaskViewHeader extends FrameLayout mMoveTaskButton.setOnClickListener(this); } - mFocusTimerIndicator.getProgressDrawable() - .setColorFilter( - getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor), - PorterDuff.Mode.SRC_IN); + if (Recents.getDebugFlags().isFastToggleIndicatorEnabled()) { + if (mFocusTimerIndicator == null) { + mFocusTimerIndicator = (ProgressBar) mFocusTimerIndicatorStub.inflate(); + } + mFocusTimerIndicator.getProgressDrawable() + .setColorFilter( + getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor), + PorterDuff.Mode.SRC_IN); + } // In accessibility, a single click on the focused app info button will show it if (ssp.isTouchExplorationEnabled()) { @@ -449,8 +485,11 @@ public class TaskViewHeader extends FrameLayout @Override public void onClick(View v) { if (v == mIconView) { - // In accessibility, a single click on the focused app info button will show it - EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); + SystemServicesProxy ssp = Recents.getSystemServices(); + if (ssp.isTouchExplorationEnabled()) { + // In accessibility, a single click on the focused app info button will show it + EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); + } } else if (v == mDismissButton) { TaskView tv = Utilities.findParent(this, TaskView.class); tv.dismissTask(); @@ -465,15 +504,92 @@ public class TaskViewHeader extends FrameLayout : new Rect(); EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, bounds, mMoveTaskTargetStackId, false)); + } else if (v == mAppInfoView) { + EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); + } else if (v == mAppIconView) { + hideAppOverlay(false /* immediate */); } } @Override public boolean onLongClick(View v) { if (v == mIconView) { - EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); + showAppOverlay(); + return true; + } else if (v == mAppIconView) { + hideAppOverlay(false /* immediate */); return true; } return false; } + + /** + * Shows the application overlay. + */ + private void showAppOverlay() { + // Skip early if the task is invalid + SystemServicesProxy ssp = Recents.getSystemServices(); + ComponentName cn = mTask.key.getComponent(); + int userId = mTask.key.userId; + ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId); + if (activityInfo == null) { + return; + } + + // Inflate the overlay if necessary + if (mAppOverlayView == null) { + mAppOverlayView = (FrameLayout) mAppOverlayViewStub.inflate(); + mAppOverlayView.setBackground(mOverlayBackground); + mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon); + mAppIconView.setOnClickListener(this); + mAppIconView.setOnLongClickListener(this); + mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info); + mAppInfoView.setOnClickListener(this); + mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title); + } + + // Update the overlay contents for the current app + mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId)); + mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo, userId)); + mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor + ? mLightInfoIcon + : mDarkInfoIcon); + mAppOverlayView.setVisibility(View.VISIBLE); + + int x = mIconView.getLeft() + mIconView.getWidth() / 2; + int y = mIconView.getTop() + mIconView.getHeight() / 2; + Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0, + getWidth()); + revealAnim.setDuration(OVERLAY_REVEAL_DURATION); + revealAnim.setInterpolator(mLinearOutSlowInInterpolator); + revealAnim.start(); + } + + /** + * Hide the application overlay. + */ + private void hideAppOverlay(boolean immediate) { + // Skip if we haven't even loaded the overlay yet + if (mAppOverlayView == null) { + return; + } + + if (immediate) { + mAppOverlayView.setVisibility(View.GONE); + } else { + int x = mIconView.getLeft() + mIconView.getWidth() / 2; + int y = mIconView.getTop() + mIconView.getHeight() / 2; + Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, + getWidth(), 0); + revealAnim.setDuration(OVERLAY_REVEAL_DURATION); + revealAnim.setInterpolator(mLinearOutSlowInInterpolator); + revealAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mAppOverlayView.setVisibility(View.GONE); + } + }); + revealAnim.start(); + } + } }