Merge "Implementing Recents focus states. (Bug 16950262)" into lmp-dev

This commit is contained in:
Winson Chung
2014-08-12 23:51:14 +00:00
committed by Android (Google) Code Review
14 changed files with 367 additions and 106 deletions

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:attr/colorControlHighlight">
<item android:drawable="@android:color/transparent" />
</ripple>

View File

@@ -26,8 +26,7 @@
android:id="@+id/task_view_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/recents_task_bar_height"
android:layout_gravity="top|center_horizontal"
android:background="@color/recents_task_bar_default_background_color">
android:layout_gravity="top|center_horizontal">
<com.android.systemui.recents.views.FixedSizeImageView
android:id="@+id/application_icon"
android:layout_width="@dimen/recents_task_view_application_icon_size"
@@ -69,7 +68,6 @@
android:layout_gravity="bottom|right"
android:layout_marginRight="15dp"
android:layout_marginBottom="15dp"
android:translationZ="50dp"
android:contentDescription="@string/recents_lock_to_app_button_label"
android:background="@drawable/recents_lock_to_task_button_bg">
<ImageView

View File

@@ -147,6 +147,8 @@
<integer name="recents_max_task_stack_view_dim">96</integer>
<!-- The number of tasks that RecentsTaskLoader should load. -->
<integer name="recents_max_num_tasks_to_load">50</integer>
<!-- The delay to enforce between each alt-tab key press. -->
<integer name="recents_alt_tab_key_delay">200</integer>
<!-- Transposes the recents layout in landscape. -->
<bool name="recents_transpose_layout_with_orientation">true</bool>

View File

@@ -345,8 +345,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
* 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 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
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 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
/** 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 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
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 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
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);

View File

@@ -61,6 +61,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
RecentsConfiguration mConfig;
boolean mVisible;
long mLastTabKeyEventTime;
// Top level views
RecentsView mRecentsView;
@@ -512,17 +513,33 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
@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);

View File

@@ -112,6 +112,9 @@ public class RecentsConfiguration {
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 @@ public class RecentsConfiguration {
// 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 */

View File

@@ -89,6 +89,17 @@ public class Utilities {
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 {

View File

@@ -20,6 +20,7 @@ import android.graphics.Color;
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 @@ public class TaskStack {
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;
}
}

View File

@@ -316,12 +316,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
/** 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 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
/** 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;
}
}
}

View File

@@ -349,9 +349,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/** 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/** 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/** 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
requestSynchronizeStackViewsWithModel();
}
@Override
public void onTaskViewFocusChanged(TaskView tv, boolean focused) {
if (focused) {
mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
}
}
/**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
@Override

View File

@@ -91,7 +91,8 @@ public class TaskStackViewLayoutAlgorithm {
/** 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 @@ public class TaskStackViewLayoutAlgorithm {
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 @@ public class TaskStackViewLayoutAlgorithm {
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;
}

View File

@@ -133,7 +133,7 @@ public class TaskStackViewScroller {
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 -

View File

@@ -48,6 +48,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
@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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
/** 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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
} 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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
}
/** 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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
@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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
.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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
} 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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
.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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
.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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
ctx.postAnimationTrigger.increment();
}
}
startDelay = mConfig.taskBarEnterAnimDelay;
} else if (mConfig.launchedFromHome) {
// Animate the tasks up
@@ -407,7 +412,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
.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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
// 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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
}
/** 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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
.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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
}
/** 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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
}
/** 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 class TaskView extends FrameLayout implements Task.TaskCallbacks,
*/
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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
@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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
@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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
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 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
@Override
public boolean onLongClick(View v) {
if (v == mBarView.mApplicationIcon) {
if (v == mHeaderView.mApplicationIcon) {
mCb.onTaskViewAppInfoClicked(this);
return true;
}

View File

@@ -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.graphics.drawable.RippleDrawable;
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 @@ class TaskViewHeader extends FrameLayout {
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 @@ class TaskViewHeader extends FrameLayout {
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 @@ class TaskViewHeader extends FrameLayout {
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 @@ class TaskViewHeader extends FrameLayout {
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 @@ class TaskViewHeader extends FrameLayout {
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 @@ class TaskViewHeader extends FrameLayout {
/** 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 @@ class TaskViewHeader extends FrameLayout {
}
/** 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 @@ class TaskViewHeader extends FrameLayout {
}
/** 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 @@ class TaskViewHeader extends FrameLayout {
}
/** 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 @@ class TaskViewHeader extends FrameLayout {
}
/** 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[] {});
}
}
}
}