Implementing Recents focus states. (Bug 16950262)
- Fixed issue with focus going to send front most task when coming from Home
- Adding shortcut to dismiss a task
- Refactoring code to get secondary overlay and affiliation color
- Throttling alt-tab key presses
- Fixing issue with slivers of the task thumbnail being visible for affiliated tasks
Change-Id: Iaafe408318646a423fd58b51bbe93dbe0f2eed99
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index efb7a2c..354eb55 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -345,8 +345,8 @@
* Creates the activity options for an app->recents transition. If this method sets the static
* screenshot, then we will use that for the transition.
*/
- ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask) {
-
+ ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask,
+ boolean isTopTaskHome) {
if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
// Recycle the last screenshot
consumeLastScreenshot();
@@ -365,7 +365,7 @@
Bitmap firstThumbnail = mSystemServicesProxy.getTaskThumbnail(topTask.id);
if (firstThumbnail != null) {
// Update the destination rect
- Rect toTaskRect = getThumbnailTransitionRect(topTask.id);
+ Rect toTaskRect = getThumbnailTransitionRect(topTask.id, isTopTaskHome);
if (toTaskRect.width() > 0 && toTaskRect.height() > 0) {
// Create the new thumbnail for the animation down
// XXX: We should find a way to optimize this so we don't need to create a new bitmap
@@ -389,7 +389,7 @@
}
/** Returns the transition rect for the given task id. */
- Rect getThumbnailTransitionRect(int runningTaskId) {
+ Rect getThumbnailTransitionRect(int runningTaskId, boolean isTopTaskHome) {
// Get the stack of tasks that we are animating into
TaskStack stack = RecentsTaskLoader.getShallowTaskStack(mSystemServicesProxy, -1);
if (stack.getTaskCount() == 0) {
@@ -401,7 +401,8 @@
TaskStackViewLayoutAlgorithm algo = tsv.getStackAlgorithm();
Rect taskStackBounds = new Rect(mTaskStackBounds);
taskStackBounds.bottom -= mSystemInsets.bottom;
- tsv.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds, mTriggeredFromAltTab);
+ tsv.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds,
+ mTriggeredFromAltTab, isTopTaskHome);
tsv.getScroller().setStackScrollToInitialState();
// Find the running task in the TaskStack
@@ -442,7 +443,7 @@
if (useThumbnailTransition) {
// Try starting with a thumbnail transition
- ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask);
+ ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, isTopTaskHome);
if (opts != null) {
if (sLastScreenshot != null) {
startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_FULL_SCREENSHOT);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 417049c..41e06de 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -61,6 +61,7 @@
RecentsConfiguration mConfig;
boolean mVisible;
+ long mLastTabKeyEventTime;
// Top level views
RecentsView mRecentsView;
@@ -512,17 +513,33 @@
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_TAB) {
- // Focus the next task in the stack
- final boolean backward = event.isShiftPressed();
- mRecentsView.focusNextTask(!backward);
- return true;
- } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
- mRecentsView.focusNextTask(true);
- return true;
- } else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
- mRecentsView.focusNextTask(false);
- return true;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_TAB: {
+ boolean hasRepKeyTimeElapsed = (System.currentTimeMillis() -
+ mLastTabKeyEventTime) > mConfig.altTabKeyDelay;
+ if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
+ // Focus the next task in the stack
+ final boolean backward = event.isShiftPressed();
+ mRecentsView.focusNextTask(!backward);
+ mLastTabKeyEventTime = System.currentTimeMillis();
+ }
+ return true;
+ }
+ case KeyEvent.KEYCODE_DPAD_UP: {
+ mRecentsView.focusNextTask(true);
+ return true;
+ }
+ case KeyEvent.KEYCODE_DPAD_DOWN: {
+ mRecentsView.focusNextTask(false);
+ return true;
+ }
+ case KeyEvent.KEYCODE_DEL:
+ case KeyEvent.KEYCODE_FORWARD_DEL: {
+ mRecentsView.dismissFocusedTask();
+ return true;
+ }
+ default:
+ break;
}
// Pass through the debug trigger
mDebugTrigger.onKeyEvent(keyCode);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 65e7076..3f5018d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -112,6 +112,9 @@
public boolean launchedFromHome;
public int launchedToTaskId;
+ /** Misc **/
+ public int altTabKeyDelay;
+
/** Dev options and global settings */
public boolean lockToAppEnabled;
public boolean developerOptionsEnabled;
@@ -250,6 +253,9 @@
// Nav bar scrim
navBarScrimEnterDuration =
res.getInteger(R.integer.recents_nav_bar_scrim_enter_duration);
+
+ // Misc
+ altTabKeyDelay = res.getInteger(R.integer.recents_alt_tab_key_delay);
}
/** Updates the system insets */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index bd5df28..4c6b389 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -89,6 +89,17 @@
return Math.abs((fgL + 0.05f) / (bgL + 0.05f));
}
+ /** Returns the base color overlaid with another overlay color with a specified alpha. */
+ public static int getColorWithOverlay(int baseColor, int overlayColor, float overlayAlpha) {
+ return Color.rgb(
+ (int) (overlayAlpha * Color.red(baseColor) +
+ (1f - overlayAlpha) * Color.red(overlayColor)),
+ (int) (overlayAlpha * Color.green(baseColor) +
+ (1f - overlayAlpha) * Color.green(overlayColor)),
+ (int) (overlayAlpha * Color.blue(baseColor) +
+ (1f - overlayAlpha) * Color.blue(overlayColor)));
+ }
+
/** Sets some private shadow properties. */
public static void setShadowProperty(String property, String value)
throws IllegalAccessException, InvocationTargetException {
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 0269141..98bf151 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -20,6 +20,7 @@
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.NamedCounter;
+import com.android.systemui.recents.misc.Utilities;
import java.util.ArrayList;
import java.util.Collections;
@@ -433,10 +434,8 @@
float alpha = 1f;
for (int j = 0; j < taskCount; j++) {
Task t = tasksMap.get(group.mTaskKeys.get(j));
- t.colorPrimary = Color.rgb(
- (int) (alpha * Color.red(affiliationColor) + (1f - alpha) * 0xFF),
- (int) (alpha * Color.green(affiliationColor) + (1f - alpha) * 0xFF),
- (int) (alpha * Color.blue(affiliationColor) + (1f - alpha) * 0xFF));
+ t.colorPrimary = Utilities.getColorWithOverlay(affiliationColor, Color.WHITE,
+ alpha);
alpha -= alphaStep;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 34e8860..07a7e74 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -316,12 +316,11 @@
/** Notifies each task view of the user interaction. */
public void onUserInteraction() {
// Get the first stack view
- TaskStackView stackView = null;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child != mSearchBar) {
- stackView = (TaskStackView) child;
+ TaskStackView stackView = (TaskStackView) child;
stackView.onUserInteraction();
}
}
@@ -330,18 +329,28 @@
/** Focuses the next task in the first stack view */
public void focusNextTask(boolean forward) {
// Get the first stack view
- TaskStackView stackView = null;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child != mSearchBar) {
- stackView = (TaskStackView) child;
+ TaskStackView stackView = (TaskStackView) child;
+ stackView.focusNextTask(forward);
break;
}
}
+ }
- if (stackView != null) {
- stackView.focusNextTask(forward);
+ /** Dismisses the focused task. */
+ public void dismissFocusedTask() {
+ // Get the first stack view
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (child != mSearchBar) {
+ TaskStackView stackView = (TaskStackView) child;
+ stackView.dismissFocusedTask();
+ break;
+ }
}
}
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 46996bb..4fd9136 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -349,9 +349,10 @@
}
/** Updates the min and max virtual scroll bounds */
- void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab) {
+ void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab,
+ boolean launchedFromHome) {
// Compute the min and max scroll values
- mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks(), launchedWithAltTab);
+ mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks(), launchedWithAltTab, launchedFromHome);
// Debug logging
if (boundScrollToNewMinMax) {
@@ -366,6 +367,9 @@
/** Focuses the task at the specified index in the stack */
void focusTask(int taskIndex, boolean scrollToNewPosition) {
+ // Return early if the task is already focused
+ if (taskIndex == mFocusedTaskIndex) return;
+
if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
mFocusedTaskIndex = taskIndex;
@@ -406,14 +410,24 @@
void focusNextTask(boolean forward) {
// Find the next index to focus
int numTasks = mStack.getTaskCount();
- if (mFocusedTaskIndex < 0) {
- mFocusedTaskIndex = numTasks - 1;
- }
+ if (numTasks == 0) return;
+
+ int nextFocusIndex = numTasks - 1;
if (0 <= mFocusedTaskIndex && mFocusedTaskIndex < numTasks) {
- mFocusedTaskIndex = Math.max(0, Math.min(numTasks - 1,
+ nextFocusIndex = Math.max(0, Math.min(numTasks - 1,
mFocusedTaskIndex + (forward ? -1 : 1)));
}
- focusTask(mFocusedTaskIndex, true);
+ focusTask(nextFocusIndex, true);
+ }
+
+ /** Dismisses the focused task. */
+ public void dismissFocusedTask() {
+ // Return early if there is no focused task index
+ if (mFocusedTaskIndex < 0) return;
+
+ Task t = mStack.getTasks().get(mFocusedTaskIndex);
+ TaskView tv = getChildViewForTask(t);
+ tv.dismissTask();
}
@Override
@@ -436,12 +450,12 @@
/** Computes the stack and task rects */
public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds,
- boolean launchedWithAltTab) {
+ boolean launchedWithAltTab, boolean launchedFromHome) {
// Compute the rects in the stack algorithm
mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds);
// Update the scroll bounds
- updateMinMaxScroll(false, launchedWithAltTab);
+ updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome);
}
/**
@@ -456,7 +470,8 @@
// Compute our stack/task rects
Rect taskStackBounds = new Rect(mTaskStackBounds);
taskStackBounds.bottom -= mConfig.systemInsets.bottom;
- computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab);
+ computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab,
+ mConfig.launchedFromHome);
// If this is the first layout, then scroll to the front of the stack and synchronize the
// stack views immediately to load all the views
@@ -546,7 +561,11 @@
// When Alt-Tabbing, we scroll to and focus the previous task
if (mConfig.launchedWithAltTab) {
- focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
+ if (mConfig.launchedFromHome) {
+ focusTask(Math.max(0, mStack.getTaskCount() - 1), false);
+ } else {
+ focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
+ }
}
}
@@ -661,7 +680,7 @@
mCb.onTaskViewDismissed(removedTask);
// Update the min/max scroll and animate other task views into their new positions
- updateMinMaxScroll(true, mConfig.launchedWithAltTab);
+ updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome);
requestSynchronizeStackViewsWithModel(200);
// Update the new front most task
@@ -859,11 +878,23 @@
@Override
public void onTaskViewDismissed(TaskView tv) {
Task task = tv.getTask();
+ int taskIndex = mStack.indexOfTask(task);
+ boolean taskWasFocused = tv.isFocusedTask();
// Announce for accessibility
tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed,
tv.getTask().activityLabel));
// Remove the task from the view
mStack.removeTask(task);
+ // If the dismissed task was focused, then we should focus the next task in front
+ if (taskWasFocused) {
+ ArrayList<Task> tasks = mStack.getTasks();
+ int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex);
+ if (nextTaskIndex >= 0) {
+ Task nextTask = tasks.get(nextTaskIndex);
+ TaskView nextTv = getChildViewForTask(nextTask);
+ nextTv.setFocusedTask();
+ }
+ }
}
@Override
@@ -876,6 +907,13 @@
requestSynchronizeStackViewsWithModel();
}
+ @Override
+ public void onTaskViewFocusChanged(TaskView tv, boolean focused) {
+ if (focused) {
+ mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
+ }
+ }
+
/**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index 6c8de72..495d00b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -91,7 +91,8 @@
/** Computes the minimum and maximum scroll progress values. This method may be called before
* the RecentsConfiguration is set, so we need to pass in the alt-tab state. */
- void computeMinMaxScroll(ArrayList<Task> tasks, boolean launchedWithAltTab) {
+ void computeMinMaxScroll(ArrayList<Task> tasks, boolean launchedWithAltTab,
+ boolean launchedFromHome) {
// Clear the progress map
mTaskProgressMap.clear();
@@ -101,10 +102,16 @@
return;
}
+ // Note that we should account for the scale difference of the offsets at the screen bottom
int taskHeight = mTaskRect.height();
float pAtBottomOfStackRect = screenYToCurveProgress(mStackVisibleRect.bottom);
- float pWithinAffiliateOffset = pAtBottomOfStackRect -
- screenYToCurveProgress(mStackVisibleRect.bottom - mWithinAffiliationOffset);
+ float pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
+ mWithinAffiliationOffset);
+ float scale = curveProgressToScale(pWithinAffiliateTop);
+ int scaleYOffset = (int) (((1f - scale) * taskHeight) / 2);
+ pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
+ mWithinAffiliationOffset + scaleYOffset);
+ float pWithinAffiliateOffset = pAtBottomOfStackRect - pWithinAffiliateTop;
float pBetweenAffiliateOffset = pAtBottomOfStackRect -
screenYToCurveProgress(mStackVisibleRect.bottom - mBetweenAffiliationOffset);
float pTaskHeightOffset = pAtBottomOfStackRect -
@@ -133,8 +140,13 @@
mMaxScrollP = pAtFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));
mMinScrollP = tasks.size() == 1 ? Math.max(mMaxScrollP, 0f) : 0f;
if (launchedWithAltTab) {
- // Center the second most task, since that will be focused first
- mInitialScrollP = pAtSecondFrontMostCardTop - 0.5f;
+ if (launchedFromHome) {
+ // Center the top most task, since that will be focused first
+ mInitialScrollP = pAtSecondFrontMostCardTop - 0.5f;
+ } else {
+ // Center the second top most task, since that will be focused first
+ mInitialScrollP = pAtSecondFrontMostCardTop - 0.5f;
+ }
} else {
mInitialScrollP = pAtFrontMostCardTop - 0.825f;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 0a12dab..2c0dc44 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -133,7 +133,7 @@
stopBoundScrollAnimation();
mScrollAnimator = ObjectAnimator.ofFloat(this, "stackScroll", curScroll, newScroll);
- mScrollAnimator.setDuration(250);
+ mScrollAnimator.setDuration(200);
// We would have to project the difference into the screen coords, and then use that as the
// duration
// mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
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 3b9bcc4..236229e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -48,6 +48,7 @@
public void onTaskViewDismissed(TaskView tv);
public void onTaskViewClipStateChanged(TaskView tv);
public void onTaskViewFullScreenTransitionCompleted();
+ public void onTaskViewFocusChanged(TaskView tv, boolean focused);
}
RecentsConfiguration mConfig;
@@ -62,13 +63,14 @@
Task mTask;
boolean mTaskDataLoaded;
boolean mIsFocused;
+ boolean mFocusAnimationsEnabled;
boolean mIsFullScreenView;
boolean mClipViewInStack;
AnimateableViewBounds mViewBounds;
Paint mLayerPaint = new Paint();
TaskViewThumbnail mThumbnailView;
- TaskViewHeader mBarView;
+ TaskViewHeader mHeaderView;
TaskViewFooter mFooterView;
View mActionButtonView;
TaskViewCallbacks mCb;
@@ -124,7 +126,7 @@
@Override
protected void onFinishInflate() {
// Bind the views
- mBarView = (TaskViewHeader) findViewById(R.id.task_view_bar);
+ mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar);
mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail);
mActionButtonView = findViewById(R.id.lock_to_app_fab);
if (mFooterView != null) {
@@ -138,7 +140,7 @@
int height = MeasureSpec.getSize(heightMeasureSpec);
// Measure the bar view, thumbnail, and footer
- mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ mHeaderView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
if (mFooterView != null) {
mFooterView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
@@ -218,11 +220,11 @@
/** Prepares this task view for the enter-recents animations. This is called earlier in the
* first layout because the actual animation into recents may take a long time. */
- public void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
+ void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
boolean occludesLaunchTarget, int offscreenY) {
if (mConfig.launchedFromAppWithScreenshot) {
if (isTaskViewLaunchTargetTask) {
- mBarView.prepareEnterRecentsAnimation();
+ mHeaderView.prepareEnterRecentsAnimation();
// Hide the footer during the transition in, and animate it out afterwards?
if (mFooterView != null) {
mFooterView.animateFooterVisibility(false, 0);
@@ -234,7 +236,7 @@
} else if (mConfig.launchedFromAppWithThumbnail) {
if (isTaskViewLaunchTargetTask) {
// Hide the front most task bar view so we can animate it in
- mBarView.prepareEnterRecentsAnimation();
+ mHeaderView.prepareEnterRecentsAnimation();
// Hide the action button if it exists
mActionButtonView.setAlpha(0f);
// Set the dim to 0 so we can animate it in
@@ -256,8 +258,9 @@
}
/** Animates this task view as it enters recents */
- public void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
+ void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
final TaskViewTransform transform = ctx.currentTaskTransform;
+ int startDelay = 0;
if (mConfig.launchedFromAppWithScreenshot) {
if (mTask.isLaunchTarget) {
@@ -269,6 +272,7 @@
float scaledWindowInsetTop = (int) (taskScale * windowInsetTop);
float scaledTranslationY = taskRect.top + transform.translationY -
(scaledWindowInsetTop + scaledYOffset);
+ startDelay = mConfig.taskViewEnterFromHomeDelay;
// Animate the top clip
mViewBounds.animateClipTop(windowInsetTop, duration,
@@ -276,7 +280,7 @@
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int y = (Integer) animation.getAnimatedValue();
- mBarView.setTranslationY(y);
+ mHeaderView.setTranslationY(y);
}
});
// Animate the bottom or right clip
@@ -287,7 +291,7 @@
mViewBounds.animateClipBottom(getMeasuredHeight() - (windowInsetTop + size), duration);
}
// Animate the task bar of the first task view
- mBarView.startEnterRecentsAnimation(0, null);
+ mHeaderView.startEnterRecentsAnimation(0, null);
animate()
.scaleX(taskScale)
.scaleY(taskScale)
@@ -304,9 +308,9 @@
mViewBounds.setClipBottom(0);
mViewBounds.setClipRight(0);
// Reset the bar translation
- mBarView.setTranslationY(0);
+ mHeaderView.setTranslationY(0);
// Enable the thumbnail clip
- mThumbnailView.enableTaskBarClip(mBarView);
+ mThumbnailView.enableTaskBarClip(mHeaderView);
// Animate the footer into view (if it is the front most task)
animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
@@ -324,7 +328,7 @@
.start();
} else {
// Otherwise, just enable the thumbnail clip
- mThumbnailView.enableTaskBarClip(mBarView);
+ mThumbnailView.enableTaskBarClip(mHeaderView);
// Animate the footer into view
animateFooterVisibility(true, 0);
@@ -334,8 +338,8 @@
} else if (mConfig.launchedFromAppWithThumbnail) {
if (mTask.isLaunchTarget) {
// Animate the task bar of the first task view
- mBarView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay,
- mThumbnailView.enableTaskBarClipAsRunnable(mBarView));
+ mHeaderView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay,
+ mThumbnailView.enableTaskBarClipAsRunnable(mHeaderView));
// Animate the dim into view as well
ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", getDimFromTaskProgress());
@@ -364,7 +368,7 @@
.start();
} else {
// Enable the task bar clip
- mThumbnailView.enableTaskBarClip(mBarView);
+ mThumbnailView.enableTaskBarClip(mHeaderView);
// Animate the task up if it was occluding the launch target
if (ctx.currentTaskOccludesLaunchTarget) {
setTranslationY(transform.translationY + mConfig.taskViewAffiliateGroupEnterOffsetPx);
@@ -378,7 +382,7 @@
.withEndAction(new Runnable() {
@Override
public void run() {
- mThumbnailView.enableTaskBarClip(mBarView);
+ mThumbnailView.enableTaskBarClip(mHeaderView);
// Decrement the post animation trigger
ctx.postAnimationTrigger.decrement();
}
@@ -387,6 +391,7 @@
ctx.postAnimationTrigger.increment();
}
}
+ startDelay = mConfig.taskBarEnterAnimDelay;
} else if (mConfig.launchedFromHome) {
// Animate the tasks up
@@ -407,7 +412,7 @@
.withEndAction(new Runnable() {
@Override
public void run() {
- mThumbnailView.enableTaskBarClip(mBarView);
+ mThumbnailView.enableTaskBarClip(mHeaderView);
// Decrement the post animation trigger
ctx.postAnimationTrigger.decrement();
}
@@ -417,18 +422,28 @@
// Animate the footer into view
animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration);
+ startDelay = delay;
} else {
// Otherwise, just enable the thumbnail clip
- mThumbnailView.enableTaskBarClip(mBarView);
+ mThumbnailView.enableTaskBarClip(mHeaderView);
// Animate the footer into view
animateFooterVisibility(true, 0);
}
+
+ // Enable the focus animations from this point onwards so that they aren't affected by the
+ // window transitions
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ enableFocusAnimations();
+ }
+ }, (startDelay / 2));
}
/** Animates this task view as it leaves recents by pressing home. */
- public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
+ void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
animate()
.translationY(ctx.offscreenTranslationY)
.setStartDelay(0)
@@ -441,11 +456,11 @@
}
/** Animates this task view as it exits recents */
- public void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask,
- boolean occludesLaunchTarget) {
+ void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask,
+ boolean occludesLaunchTarget) {
if (isLaunchingTask) {
// Disable the thumbnail clip and animate the bar out
- mBarView.startLaunchTaskAnimation(mThumbnailView.disableTaskBarClipAsRunnable(), r);
+ mHeaderView.startLaunchTaskAnimation(mThumbnailView.disableTaskBarClipAsRunnable(), r);
// Animate the dim
if (mDim > 0) {
@@ -464,7 +479,7 @@
.start();
} else {
// Hide the dismiss button
- mBarView.startLaunchTaskDismissAnimation();
+ mHeaderView.startLaunchTaskDismissAnimation();
// If this is another view in the task grouping and is in front of the launch task,
// animate it away first
if (occludesLaunchTarget) {
@@ -480,7 +495,7 @@
}
/** Animates the deletion of this task view */
- public void startDeleteTaskAnimation(final Runnable r) {
+ void startDeleteTaskAnimation(final Runnable r) {
// Disabling clipping with the stack while the view is animating away
setClipViewInStack(false);
@@ -508,19 +523,33 @@
}
/** Animates this task view if the user does not interact with the stack after a certain time. */
- public void startNoUserInteractionAnimation() {
- mBarView.startNoUserInteractionAnimation();
+ void startNoUserInteractionAnimation() {
+ mHeaderView.startNoUserInteractionAnimation();
}
/** Mark this task view that the user does has not interacted with the stack after a certain time. */
- public void setNoUserInteractionState() {
- mBarView.setNoUserInteractionState();
+ void setNoUserInteractionState() {
+ mHeaderView.setNoUserInteractionState();
+ }
+
+ /** Dismisses this task. */
+ void dismissTask() {
+ // Animate out the view and call the callback
+ final TaskView tv = this;
+ startDeleteTaskAnimation(new Runnable() {
+ @Override
+ public void run() {
+ mCb.onTaskViewDismissed(tv);
+ }
+ });
+ // Hide the footer
+ animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration);
}
/** Sets whether this task view is full screen or not. */
void setIsFullScreen(boolean isFullscreen) {
mIsFullScreenView = isFullscreen;
- mBarView.setIsFullscreen(isFullscreen);
+ mHeaderView.setIsFullscreen(isFullscreen);
if (isFullscreen) {
// If we are full screen, then disable the bottom outline clip for the footer
mViewBounds.setOutlineClipBottom(0);
@@ -614,6 +643,12 @@
*/
public void setFocusedTask() {
mIsFocused = true;
+ if (mFocusAnimationsEnabled) {
+ // Focus the header bar
+ mHeaderView.onTaskViewFocusChanged(true);
+ }
+ // Call the callback
+ mCb.onTaskViewFocusChanged(this, true);
// Workaround, we don't always want it focusable in touch mode, but we want the first task
// to be focused after the enter-recents animation, which can be triggered from either touch
// or keyboard
@@ -631,6 +666,12 @@
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
if (!gainFocus) {
mIsFocused = false;
+ if (mFocusAnimationsEnabled) {
+ // Un-focus the header bar
+ mHeaderView.onTaskViewFocusChanged(false);
+ }
+ // Call the callback
+ mCb.onTaskViewFocusChanged(this, false);
invalidate();
}
}
@@ -642,6 +683,16 @@
return mIsFocused || isFocused();
}
+ /** Enables all focus animations. */
+ void enableFocusAnimations() {
+ boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled;
+ mFocusAnimationsEnabled = true;
+ if (mIsFocused && !wasFocusAnimationsEnabled) {
+ // Re-notify the header if we were focused and animations were not previously enabled
+ mHeaderView.onTaskViewFocusChanged(true);
+ }
+ }
+
/**** TaskCallbacks Implementation ****/
/** Binds this task view to the task */
@@ -662,24 +713,24 @@
@Override
public void onTaskDataLoaded() {
- if (mThumbnailView != null && mBarView != null) {
+ if (mThumbnailView != null && mHeaderView != null) {
// Bind each of the views to the new task data
if (mIsFullScreenView) {
mThumbnailView.bindToScreenshot(AlternateRecentsComponent.getLastScreenshot());
} else {
mThumbnailView.rebindToTask(mTask);
}
- mBarView.rebindToTask(mTask);
+ mHeaderView.rebindToTask(mTask);
// Rebind any listeners
- mBarView.mApplicationIcon.setOnClickListener(this);
- mBarView.mDismissButton.setOnClickListener(this);
+ mHeaderView.mApplicationIcon.setOnClickListener(this);
+ mHeaderView.mDismissButton.setOnClickListener(this);
if (mFooterView != null) {
mFooterView.setOnClickListener(this);
}
mActionButtonView.setOnClickListener(this);
if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
if (mConfig.developerOptionsEnabled) {
- mBarView.mApplicationIcon.setOnLongClickListener(this);
+ mHeaderView.mApplicationIcon.setOnLongClickListener(this);
}
}
}
@@ -688,20 +739,20 @@
@Override
public void onTaskDataUnloaded() {
- if (mThumbnailView != null && mBarView != null) {
+ if (mThumbnailView != null && mHeaderView != null) {
// Unbind each of the views from the task data and remove the task callback
mTask.setCallbacks(null);
mThumbnailView.unbindFromTask();
- mBarView.unbindFromTask();
+ mHeaderView.unbindFromTask();
// Unbind any listeners
- mBarView.mApplicationIcon.setOnClickListener(null);
- mBarView.mDismissButton.setOnClickListener(null);
+ mHeaderView.mApplicationIcon.setOnClickListener(null);
+ mHeaderView.mDismissButton.setOnClickListener(null);
if (mFooterView != null) {
mFooterView.setOnClickListener(null);
}
mActionButtonView.setOnClickListener(null);
if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
- mBarView.mApplicationIcon.setOnLongClickListener(null);
+ mHeaderView.mApplicationIcon.setOnLongClickListener(null);
}
}
mTaskDataLoaded = false;
@@ -734,18 +785,10 @@
postDelayed(new Runnable() {
@Override
public void run() {
- if (Constants.DebugFlags.App.EnableTaskFiltering && v == mBarView.mApplicationIcon) {
+ if (Constants.DebugFlags.App.EnableTaskFiltering && v == mHeaderView.mApplicationIcon) {
mCb.onTaskViewAppIconClicked(tv);
- } else if (v == mBarView.mDismissButton) {
- // Animate out the view and call the callback
- startDeleteTaskAnimation(new Runnable() {
- @Override
- public void run() {
- mCb.onTaskViewDismissed(tv);
- }
- });
- // Hide the footer
- tv.animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration);
+ } else if (v == mHeaderView.mDismissButton) {
+ dismissTask();
} else {
mCb.onTaskViewClicked(tv, tv.getTask(),
(v == mFooterView || v == mActionButtonView));
@@ -758,7 +801,7 @@
@Override
public boolean onLongClick(View v) {
- if (v == mBarView.mApplicationIcon) {
+ if (v == mHeaderView.mApplicationIcon) {
mCb.onTaskViewAppInfoClicked(this);
return true;
}
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 03fc16e..4b09549 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -16,9 +16,16 @@
package com.android.systemui.recents.views;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
@@ -28,12 +35,14 @@
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
@@ -46,10 +55,15 @@
ImageView mApplicationIcon;
TextView mActivityDescription;
+ RippleDrawable mBackground;
+ ColorDrawable mBackgroundColor;
Drawable mLightDismissDrawable;
Drawable mDarkDismissDrawable;
+ ValueAnimator mBackgroundColorAnimator;
boolean mIsFullscreen;
+ boolean mCurrentPrimaryColorIsDark;
+ int mCurrentPrimaryColor;
static Paint sHighlightPaint;
@@ -69,6 +83,13 @@
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = RecentsConfiguration.getInstance();
setWillNotDraw(false);
+ setClipToOutline(true);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
+ }
+ });
// Load the dismiss resources
Resources res = context.getResources();
@@ -107,6 +128,15 @@
mApplicationIcon.setBackground(null);
}
}
+
+ mBackgroundColor = new ColorDrawable(0);
+ // Copy the ripple drawable since we are going to be manipulating it
+ mBackground = (RippleDrawable)
+ getResources().getDrawable(R.drawable.recents_task_view_header_bg);
+ mBackground = (RippleDrawable) mBackground.mutate().getConstantState().newDrawable();
+ mBackground.setColor(ColorStateList.valueOf(0));
+ mBackground.setDrawableByLayerId(mBackground.getId(0), mBackgroundColor);
+ setBackground(mBackground);
}
@Override
@@ -130,6 +160,12 @@
return false;
}
+ /** Returns the secondary color for a primary color. */
+ int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
+ int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
+ return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
+ }
+
/** Binds the bar view to the task */
void rebindToTask(Task t) {
// If an activity icon is defined, then we use that as the primary icon to show in the bar,
@@ -147,8 +183,10 @@
int existingBgColor = (getBackground() instanceof ColorDrawable) ?
((ColorDrawable) getBackground()).getColor() : 0;
if (existingBgColor != t.colorPrimary) {
- setBackgroundColor(t.colorPrimary);
+ mBackgroundColor.setColor(t.colorPrimary);
}
+ mCurrentPrimaryColor = t.colorPrimary;
+ mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor;
mActivityDescription.setTextColor(t.useLightOnPrimaryColor ?
mConfig.taskBarViewLightTextColor : mConfig.taskBarViewDarkTextColor);
mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
@@ -165,12 +203,12 @@
/** Prepares this task view for the enter-recents animations. This is called earlier in the
* first layout because the actual animation into recents may take a long time. */
- public void prepareEnterRecentsAnimation() {
+ void prepareEnterRecentsAnimation() {
setVisibility(View.INVISIBLE);
}
/** Animates this task bar as it enters recents */
- public void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) {
+ void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) {
// Animate the task bar of the first task view
setVisibility(View.VISIBLE);
setTranslationY(-getMeasuredHeight());
@@ -184,7 +222,7 @@
}
/** Animates this task bar as it exits recents */
- public void startLaunchTaskAnimation(Runnable preAnimRunnable, final Runnable postAnimRunnable) {
+ void startLaunchTaskAnimation(Runnable preAnimRunnable, final Runnable postAnimRunnable) {
// Animate the task bar out of the first task view
animate()
.translationY(-getMeasuredHeight())
@@ -202,7 +240,7 @@
}
/** Animates this task bar dismiss button when launching a task. */
- public void startLaunchTaskDismissAnimation() {
+ void startLaunchTaskDismissAnimation() {
if (mDismissButton.getVisibility() == View.VISIBLE) {
mDismissButton.animate().cancel();
mDismissButton.animate()
@@ -216,7 +254,7 @@
}
/** Animates this task bar if the user does not interact with the stack after a certain time. */
- public void startNoUserInteractionAnimation() {
+ void startNoUserInteractionAnimation() {
mDismissButton.setVisibility(View.VISIBLE);
mDismissButton.setAlpha(0f);
mDismissButton.animate()
@@ -229,11 +267,78 @@
}
/** Mark this task view that the user does has not interacted with the stack after a certain time. */
- public void setNoUserInteractionState() {
+ void setNoUserInteractionState() {
if (mDismissButton.getVisibility() != View.VISIBLE) {
mDismissButton.animate().cancel();
mDismissButton.setVisibility(View.VISIBLE);
mDismissButton.setAlpha(1f);
}
}
+
+ /** Notifies the associated TaskView has been focused. */
+ void onTaskViewFocusChanged(boolean focused) {
+ boolean isRunning = false;
+ if (mBackgroundColorAnimator != null) {
+ isRunning = mBackgroundColorAnimator.isRunning();
+ mBackgroundColorAnimator.removeAllUpdateListeners();
+ mBackgroundColorAnimator.cancel();
+ }
+ if (focused) {
+ int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
+ int[][] states = new int[][] {
+ new int[] { android.R.attr.state_enabled },
+ new int[] { android.R.attr.state_pressed }
+ };
+ int[] newStates = new int[]{
+ android.R.attr.state_enabled,
+ android.R.attr.state_pressed
+ };
+ int[] colors = new int[] {
+ secondaryColor,
+ secondaryColor
+ };
+ mBackground.setColor(new ColorStateList(states, colors));
+ mBackground.setState(newStates);
+ // Pulse the background color
+ int currentColor = mBackgroundColor.getColor();
+ int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
+ mBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), lightPrimaryColor,
+ currentColor);
+ mBackgroundColorAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mBackground.setState(new int[] {});
+ }
+ });
+ mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mBackgroundColor.setColor((Integer) animation.getAnimatedValue());
+ }
+ });
+ mBackgroundColorAnimator.setRepeatCount(ValueAnimator.INFINITE);
+ mBackgroundColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ mBackgroundColorAnimator.setStartDelay(750);
+ mBackgroundColorAnimator.setDuration(750);
+ mBackgroundColorAnimator.start();
+ } else {
+ if (isRunning) {
+ // Restore the background color
+ int currentColor = mBackgroundColor.getColor();
+ mBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), currentColor,
+ mCurrentPrimaryColor);
+ mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mBackgroundColor.setColor((Integer) animation.getAnimatedValue());
+ }
+ });
+ mBackgroundColorAnimator.setRepeatCount(0);
+ mBackgroundColorAnimator.setDuration(150);
+ mBackgroundColorAnimator.start();
+ } else {
+ mBackground.setState(new int[] {});
+ }
+ }
+ }
}