Intermediate refactoring to move towards in-app view transitions.

- Fixing bug where we weren't toggling to the right task when using affiliations
- Refactoring task rect calculation to allow full screen task view to be laid out for transitions
- Refactoring the view bounds animations into a separate class
- Refactoring the footer view (for lock-to-task) out of TaskView
- Refactoring some transform code out of TaskView
- Removing fullscreen overlay view
- Fixing case where extra invalidations and layouts were still happening in FixedSizeImageView
- Adding debug overlay to replace specific debug drawing code

Change-Id: Ibf98b6a0782a68cd84582203c807cece1ff3379f
This commit is contained in:
Winson Chung
2014-07-22 12:27:13 -07:00
parent 8d7f8a253d
commit dcfa7976fa
19 changed files with 936 additions and 902 deletions

View File

@ -62,7 +62,7 @@
android:visibility="invisible"
android:src="@drawable/recents_dismiss_light" />
</com.android.systemui.recents.views.TaskBarView>
<FrameLayout
<com.android.systemui.recents.views.TaskFooterView
android:id="@+id/lock_to_app"
android:layout_width="match_parent"
android:layout_height="@dimen/recents_task_view_lock_to_app_button_height"
@ -82,7 +82,7 @@
android:fontFamily="sans-serif-medium"
android:singleLine="true"
android:textAllCaps="true" />
</FrameLayout>
</com.android.systemui.recents.views.TaskFooterView>
</com.android.systemui.recents.views.TaskView>

View File

@ -23,6 +23,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
@ -70,10 +71,13 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
// Task launching
RecentsConfiguration mConfig;
Rect mWindowRect;
Rect mTaskStackBounds;
Rect mWindowRect = new Rect();
Rect mTaskStackBounds = new Rect();
Rect mSystemInsets = new Rect();
TaskViewTransform mTmpTransform = new TaskViewTransform();
int mStatusBarHeight;
int mNavBarHeight;
int mNavBarWidth;
// Variables to keep track of if we need to start recents after binding
View mStatusBarView;
@ -81,15 +85,23 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
long mLastToggleTime;
public AlternateRecentsComponent(Context context) {
Resources res = context.getResources();
mContext = context;
mSystemServicesProxy = new SystemServicesProxy(context);
mHandler = new Handler();
mConfig = RecentsConfiguration.reinitialize(context, mSystemServicesProxy);
mWindowRect = mSystemServicesProxy.getWindowRect();
mTaskStackBounds = new Rect();
mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mTaskStackBounds);
mStatusBarHeight = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height);
mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mStatusBarHeight,
mNavBarWidth, mTaskStackBounds);
if (mConfig.isLandscape && mConfig.transposeRecentsLayoutWithOrientation) {
mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0);
} else {
mSystemInsets.set(0, mStatusBarHeight, 0, mNavBarHeight);
}
}
public void onStart() {
@ -150,7 +162,13 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
mConfig = RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy);
mConfig.updateOnConfigurationChange();
mWindowRect = mSystemServicesProxy.getWindowRect();
mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mTaskStackBounds);
mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mStatusBarHeight,
mNavBarWidth, mTaskStackBounds);
if (mConfig.isLandscape && mConfig.transposeRecentsLayoutWithOrientation) {
mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0);
} else {
mSystemInsets.set(0, mStatusBarHeight, 0, mNavBarHeight);
}
sLastScreenshot = null;
}
@ -301,7 +319,9 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
// Get the stack
TaskStackView tsv = new TaskStackView(mContext, stack);
TaskStackViewLayoutAlgorithm algo = tsv.getStackAlgorithm();
tsv.computeRects(mTaskStackBounds.width(), mTaskStackBounds.height() - mStatusBarHeight, 0, 0);
Rect taskStackBounds = new Rect(mTaskStackBounds);
taskStackBounds.bottom -= mSystemInsets.bottom;
tsv.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds);
tsv.setStackScrollToInitialState();
// Find the running task in the TaskStack
@ -325,8 +345,6 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
// Get the transform for the running task
mTmpTransform = algo.getStackTransform(task, tsv.getStackScroll(), mTmpTransform);
mTmpTransform.rect.offset(mTaskStackBounds.left, mTaskStackBounds.top);
mTmpTransform.rect.offset(0, mStatusBarHeight);
return new Rect(mTmpTransform.rect);
}

View File

@ -32,18 +32,20 @@ import android.os.UserHandle;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.FrameLayout;
import android.widget.Toast;
import com.android.systemui.R;
import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.DebugTrigger;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.SpaceNode;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.views.FullscreenTransitionOverlayView;
import com.android.systemui.recents.views.DebugOverlayView;
import com.android.systemui.recents.views.RecentsView;
import com.android.systemui.recents.views.SystemBarScrimViews;
import com.android.systemui.recents.views.ViewAnimation;
@ -56,8 +58,7 @@ import java.util.ArrayList;
* The main Recents activity that is started from AlternateRecentsComponent.
*/
public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks,
FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks {
RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks {
// Actions and Extras sent from AlternateRecentsComponent
final static String EXTRA_TRIGGERED_FROM_ALT_TAB = "extra_triggered_from_alt_tab";
@ -73,17 +74,14 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
SystemBarScrimViews mScrimViews;
ViewStub mEmptyViewStub;
View mEmptyView;
ViewStub mFullscreenOverlayStub;
FullscreenTransitionOverlayView mFullScreenOverlayView;
DebugOverlayView mDebugOverlay;
// Search AppWidget
RecentsAppWidgetHost mAppWidgetHost;
AppWidgetProviderInfo mSearchAppWidgetInfo;
AppWidgetHostView mSearchAppWidgetHostView;
// Runnables to finish the Recents activity
FinishRecentsRunnable mFinishRunnable = new FinishRecentsRunnable();
FinishRecentsRunnable mFinishLaunchHomeRunnable;
/**
@ -97,10 +95,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
Intent mLaunchIntent;
ActivityOptions mLaunchOpts;
public FinishRecentsRunnable() {
// Do nothing
}
/**
* Creates a finish runnable that starts the specified intent, using the given
* ActivityOptions.
@ -151,8 +145,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
} else if (action.equals(ACTION_START_ENTER_ANIMATION)) {
// Try and start the enter animation (or restart it on configuration changed)
ReferenceCountedTrigger t = new ReferenceCountedTrigger(context, null, null, null);
mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(
mFullScreenOverlayView, t));
mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(t));
onEnterAnimationTriggered();
}
}
@ -187,11 +180,12 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
/** Updates the set of recent tasks */
void updateRecentsTasks(Intent launchIntent) {
// Load all the tasks
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SpaceNode root = loader.reload(this, Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount);
ArrayList<TaskStack> stacks = root.getStacks();
if (!stacks.isEmpty()) {
mRecentsView.setBSP(root);
mRecentsView.setTaskStacks(root.getStacks());
}
// Update the configuration based on the launch intent
@ -207,6 +201,23 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
mConfig.launchedToTaskId = launchIntent.getIntExtra(
AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_TASK_ID, -1);
// Mark the task that is the launch target
int taskStackCount = stacks.size();
if (mConfig.launchedToTaskId != -1) {
for (int i = 0; i < taskStackCount; i++) {
TaskStack stack = stacks.get(i);
ArrayList<Task> tasks = stack.getTasks();
int taskCount = tasks.size();
for (int j = 0; j < taskCount; j++) {
Task t = tasks.get(j);
if (t.key.id == mConfig.launchedToTaskId) {
t.isLaunchTarget = true;
break;
}
}
}
}
// Update the top level view's visibilities
if (mConfig.launchedWithNoRecentTasks) {
if (mEmptyView == null) {
@ -286,9 +297,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
/** Dismisses recents if we are already visible and the intent is to toggle the recents view */
boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
if (mVisible) {
// If we are mid-animation into Recents, reverse the animation now
if (mFullScreenOverlayView != null &&
mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) return true;
// If we currently have filtered stacks, then unfilter those first
if (checkFilteredStackState &&
mRecentsView.unfilterFilteredStacks()) return true;
@ -299,8 +307,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
dismissRecentsToHomeRaw(true);
return true;
}
// Otherwise, try and return to the first Task in the stack
if (mRecentsView.launchFirstTask()) return true;
// Otherwise, try and return to the Task that Recents was launched from
if (mRecentsView.launchPreviousTask()) return true;
// If none of the other cases apply, then just go Home
dismissRecentsToHomeRaw(true);
return true;
@ -323,9 +331,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
/** Dismisses Recents directly to Home if we currently aren't transitioning. */
boolean dismissRecentsToHome(boolean animated) {
if (mVisible) {
// If we are mid-animation into Recents, reverse the animation now
if (mFullScreenOverlayView != null &&
mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) return true;
// Return to Home
dismissRecentsToHomeRaw(animated);
return true;
@ -363,8 +368,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
mFullscreenOverlayStub = (ViewStub) findViewById(R.id.fullscreen_overlay_stub);
mScrimViews = new SystemBarScrimViews(this, mConfig);
inflateDebugOverlay();
// Bind the search app widget when we first start up
bindSearchBarAppWidget();
@ -390,13 +395,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
e.printStackTrace();
}
// Prepare the screenshot transition if necessary
if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
mFullScreenOverlayView = (FullscreenTransitionOverlayView) mFullscreenOverlayStub.inflate();
mFullScreenOverlayView.setCallbacks(this);
mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
}
// Update if we are getting a configuration change
if (savedInstanceState != null) {
mConfig.updateOnConfigurationChange();
@ -404,6 +402,19 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
}
}
/** Inflates the debug overlay if debug mode is enabled. */
void inflateDebugOverlay() {
if (mConfig.debugModeEnabled && mDebugOverlay == null) {
ViewGroup parent = (ViewGroup) findViewById(android.R.id.content).getRootView();
mDebugOverlay = new DebugOverlayView(this);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
parent.addView(mDebugOverlay, lp);
mRecentsView.setDebugOverlay(mDebugOverlay);
}
}
void onConfigurationChange() {
// Update RecentsConfiguration
mConfig = RecentsConfiguration.reinitialize(this,
@ -411,8 +422,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
// Try and start the enter animation (or restart it on configuration changed)
ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(
mFullScreenOverlayView, t));
mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(t));
onEnterAnimationTriggered();
}
@ -421,13 +431,13 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
super.onNewIntent(intent);
setIntent(intent);
// Clear any debug rects
if (mDebugOverlay != null) {
mDebugOverlay.clear();
}
// Update the recent tasks
updateRecentsTasks(intent);
// Prepare the screenshot transition if necessary
if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
}
}
@Override
@ -531,17 +541,25 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
/** Called when debug mode is triggered */
public void onDebugModeTriggered() {
if (mConfig.developerOptionsEnabled) {
SharedPreferences settings = getSharedPreferences(getPackageName(), 0);
if (settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false)) {
// Disable the debug mode
settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply();
mConfig.debugModeEnabled = false;
inflateDebugOverlay();
mDebugOverlay.disable();
} else {
// Enable the debug mode
settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply();
mConfig.debugModeEnabled = true;
inflateDebugOverlay();
mDebugOverlay.enable();
}
Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion +
") toggled, please restart Recents now", Toast.LENGTH_SHORT).show();
Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion + ") " +
(mConfig.debugModeEnabled ? "Enabled" : "Disabled") + ", please restart Recents now",
Toast.LENGTH_SHORT).show();
}
}
@ -551,19 +569,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
mScrimViews.startEnterRecentsAnimation();
}
/**** FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks Implementation ****/
@Override
public void onEnterAnimationComplete() {
// Reset the full screenshot transition view
if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
mFullScreenOverlayView.reset();
// Recycle the full screen screenshot
AlternateRecentsComponent.consumeLastScreenshot();
}
}
/**** RecentsView.RecentsViewCallbacks Implementation ****/
@Override

View File

@ -309,19 +309,16 @@ public class RecentsConfiguration {
* Returns the task stack bounds in the current orientation. These bounds do not account for
* the system insets.
*/
public void getTaskStackBounds(int width, int height, Rect taskStackBounds) {
if (hasSearchBarAppWidget()) {
Rect searchBarBounds = new Rect();
getSearchBarBounds(width, height, searchBarBounds);
if (isLandscape && transposeRecentsLayoutWithOrientation) {
// In landscape, the search bar appears on the left, so shift the task rect right
taskStackBounds.set(searchBarBounds.width(), 0, width, height);
} else {
// In portrait, the search bar appears on the top, so shift the task rect below
taskStackBounds.set(0, searchBarBounds.height(), width, height);
}
public void getTaskStackBounds(int windowWidth, int windowHeight, int topInset, int rightInset,
Rect taskStackBounds) {
Rect searchBarBounds = new Rect();
getSearchBarBounds(windowWidth, windowHeight, topInset, searchBarBounds);
if (isLandscape && transposeRecentsLayoutWithOrientation) {
// In landscape, the search bar appears on the left
taskStackBounds.set(searchBarBounds.right, topInset, windowWidth - rightInset, windowHeight);
} else {
taskStackBounds.set(0, 0, width, height);
// In portrait, the search bar appears on the top (which already has the inset)
taskStackBounds.set(0, searchBarBounds.bottom, windowWidth, windowHeight);
}
}
@ -329,19 +326,20 @@ public class RecentsConfiguration {
* Returns the search bar bounds in the current orientation. These bounds do not account for
* the system insets.
*/
public void getSearchBarBounds(int width, int height, Rect searchBarSpaceBounds) {
public void getSearchBarBounds(int windowWidth, int windowHeight, int topInset,
Rect searchBarSpaceBounds) {
// Return empty rects if search is not enabled
if (!Constants.DebugFlags.App.EnableSearchLayout) {
searchBarSpaceBounds.set(0, 0, 0, 0);
return;
int searchBarSize = searchBarSpaceHeightPx;
if (!Constants.DebugFlags.App.EnableSearchLayout || !hasSearchBarAppWidget()) {
searchBarSize = 0;
}
if (isLandscape && transposeRecentsLayoutWithOrientation) {
// In landscape, the search bar appears on the left
searchBarSpaceBounds.set(0, 0, searchBarSpaceHeightPx, height);
searchBarSpaceBounds.set(0, topInset, searchBarSize, windowHeight);
} else {
// In portrait, the search bar appears on the top
searchBarSpaceBounds.set(0, 0, width, searchBarSpaceHeightPx);
searchBarSpaceBounds.set(0, topInset, windowWidth, topInset + searchBarSize);
}
}
}

View File

@ -38,6 +38,7 @@ import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
@ -437,7 +438,9 @@ public class SystemServicesProxy {
Rect windowRect = new Rect();
if (mWm == null) return windowRect;
mWm.getDefaultDisplay().getRectSize(windowRect);
Point p = new Point();
mWm.getDefaultDisplay().getRealSize(p);
windowRect.set(0, 0, p.x, p.y);
return windowRect;
}

View File

@ -77,6 +77,7 @@ public class Task {
public TaskKey key;
public TaskGrouping group;
public int taskAffiliation;
public boolean isLaunchTarget;
public Drawable applicationIcon;
public Drawable activityIcon;
public String activityLabel;

View File

@ -0,0 +1,118 @@
/*
* 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.
*/
package com.android.systemui.recents.views;
import android.animation.ObjectAnimator;
import android.graphics.Outline;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewOutlineProvider;
import com.android.systemui.recents.RecentsConfiguration;
/* An outline provider that has a clip and outline that can be animated. */
public class AnimateableViewBounds extends ViewOutlineProvider {
RecentsConfiguration mConfig;
View mSourceView;
Rect mClipRect = new Rect();
Rect mOutlineClipRect = new Rect();
int mCornerRadius;
ObjectAnimator mClipTopAnimator;
ObjectAnimator mClipBottomAnimator;
public AnimateableViewBounds(View source, int cornerRadius) {
mConfig = RecentsConfiguration.getInstance();
mSourceView = source;
mCornerRadius = cornerRadius;
mSourceView.setClipToOutline(true);
setClipTop(getClipTop());
setClipBottom(getClipBottom());
setOutlineClipBottom(getOutlineClipBottom());
}
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(Math.max(mClipRect.left, mOutlineClipRect.left),
Math.max(mClipRect.top, mOutlineClipRect.top),
mSourceView.getMeasuredWidth() - Math.max(mClipRect.right, mOutlineClipRect.right),
mSourceView.getMeasuredHeight() - Math.max(mClipRect.bottom, mOutlineClipRect.bottom),
mCornerRadius);
}
/** Animates the top clip. */
void animateClipTop(int top, int duration) {
if (mClipTopAnimator != null) {
mClipTopAnimator.removeAllListeners();
mClipTopAnimator.cancel();
}
mClipTopAnimator = ObjectAnimator.ofInt(this, "clipTop", top);
mClipTopAnimator.setDuration(duration);
mClipTopAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
mClipTopAnimator.start();
}
/** Sets the top clip. */
public void setClipTop(int top) {
if (top != mClipRect.top) {
mClipRect.top = top;
mSourceView.invalidateOutline();
}
}
/** Returns the top clip. */
public int getClipTop() {
return mClipRect.top;
}
/** Animates the bottom clip. */
void animateClipBottom(int bottom, int duration) {
if (mClipTopAnimator != null) {
mClipTopAnimator.removeAllListeners();
mClipTopAnimator.cancel();
}
mClipTopAnimator = ObjectAnimator.ofInt(this, "clipBottom", bottom);
mClipTopAnimator.setDuration(duration);
mClipTopAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
mClipTopAnimator.start();
}
/** Sets the bottom clip. */
public void setClipBottom(int bottom) {
if (bottom != mClipRect.bottom) {
mClipRect.bottom = bottom;
mSourceView.invalidateOutline();
}
}
/** Returns the bottom clip. */
public int getClipBottom() {
return mClipRect.bottom;
}
public void setOutlineClipBottom(int bottom) {
if (bottom != mOutlineClipRect.bottom) {
mOutlineClipRect.bottom = bottom;
mSourceView.invalidateOutline();
}
}
public int getOutlineClipBottom() {
return mOutlineClipRect.bottom;
}
}

View File

@ -0,0 +1,113 @@
/*
* 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.
*/
package com.android.systemui.recents.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.Pair;
import android.view.View;
import android.widget.FrameLayout;
import java.util.ArrayList;
/**
* A full screen overlay layer that allows us to draw views from throughout the system on the top
* most layer.
*/
public class DebugOverlayView extends FrameLayout {
final static int sCornerRectSize = 50;
ArrayList<Pair<Rect, Integer>> mRects = new ArrayList<Pair<Rect, Integer>>();
Paint mDebugOutline = new Paint();
Paint mTmpPaint = new Paint();
boolean mEnabled = true;
public DebugOverlayView(Context context) {
super(context);
mDebugOutline.setColor(0xFFff0000);
mDebugOutline.setStyle(Paint.Style.STROKE);
mDebugOutline.setStrokeWidth(8f);
setWillNotDraw(false);
}
/** Enables the debug overlay drawing. */
public void enable() {
mEnabled = true;
invalidate();
}
/** Disables the debug overlay drawing. */
public void disable() {
mEnabled = false;
invalidate();
}
/** Clears all debug rects. */
public void clear() {
mRects.clear();
}
/** Adds a rect to be drawn. */
void addRect(Rect r, int color) {
mRects.add(new Pair<Rect, Integer>(r, color));
invalidate();
}
/** Adds a view's global rect to be drawn. */
void addViewRect(View v, int color) {
Rect vr = new Rect();
v.getGlobalVisibleRect(vr);
mRects.add(new Pair<Rect, Integer>(vr, color));
invalidate();
}
/** Adds a rect, relative to a given view to be drawn. */
void addRectRelativeToView(View v, Rect r, int color) {
Rect vr = new Rect();
v.getGlobalVisibleRect(vr);
r.offsetTo(vr.left, vr.top);
mRects.add(new Pair<Rect, Integer>(r, color));
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
addRect(new Rect(0, 0, sCornerRectSize, sCornerRectSize), 0xFFff0000);
addRect(new Rect(getMeasuredWidth() - sCornerRectSize, getMeasuredHeight() - sCornerRectSize,
getMeasuredWidth(), getMeasuredHeight()), 0xFFff0000);
}
@Override
protected void onDraw(Canvas canvas) {
if (mEnabled) {
// Draw the outline
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mDebugOutline);
// Draw the rects
int numRects = mRects.size();
for (int i = 0; i < numRects; i++) {
Pair<Rect, Integer> r = mRects.get(i);
mTmpPaint.setColor(r.second);
canvas.drawRect(r.first, mTmpPaint);
}
}
}
}

View File

@ -17,6 +17,7 @@
package com.android.systemui.recents.views;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
@ -27,8 +28,6 @@ import android.widget.ImageView;
*/
public class FixedSizeImageView extends ImageView {
int mFixedWidth;
int mFixedHeight;
boolean mAllowRelayout = true;
boolean mAllowInvalidate = true;
@ -48,13 +47,6 @@ public class FixedSizeImageView extends ImageView {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mFixedWidth = getMeasuredWidth();
mFixedHeight = getMeasuredHeight();
}
@Override
public void requestLayout() {
if (mAllowRelayout) {
@ -71,7 +63,9 @@ public class FixedSizeImageView extends ImageView {
@Override
public void setImageDrawable(Drawable drawable) {
if (drawable == null || (mFixedWidth > 0 && mFixedHeight > 0)) {
boolean isNullBitmapDrawable = (drawable instanceof BitmapDrawable) &&
(((BitmapDrawable) drawable).getBitmap() == null);
if (drawable == null || isNullBitmapDrawable) {
mAllowRelayout = false;
mAllowInvalidate = false;
}

View File

@ -1,285 +0,0 @@
/*
* 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.
*/
package com.android.systemui.recents.views;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.systemui.R;
import com.android.systemui.recents.RecentsConfiguration;
/**
* The full screen transition view that gets animated down from the full screen into a task
* thumbnail view.
*/
public class FullscreenTransitionOverlayView extends FrameLayout {
/** The FullscreenTransitionOverlayView callbacks */
public interface FullScreenTransitionViewCallbacks {
void onEnterAnimationComplete();
}
RecentsConfiguration mConfig;
FullScreenTransitionViewCallbacks mCb;
ImageView mScreenshotView;
Rect mClipRect = new Rect();
Paint mLayerPaint = new Paint();
PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.MULTIPLY);
int mDim;
int mMaxDim;
AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator();
boolean mIsAnimating;
AnimatorSet mEnterAnimation;
public FullscreenTransitionOverlayView(Context context) {
super(context);
}
public FullscreenTransitionOverlayView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FullscreenTransitionOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public FullscreenTransitionOverlayView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = RecentsConfiguration.getInstance();
mMaxDim = mConfig.taskStackMaxDim;
setClipTop(getClipTop());
setClipBottom(getClipBottom());
setDim(getDim());
setWillNotDraw(false);
}
@Override
protected void onFinishInflate() {
mScreenshotView = (ImageView) findViewById(R.id.image);
}
/** Sets the callbacks */
public void setCallbacks(FullScreenTransitionViewCallbacks cb) {
mCb = cb;
}
/** Sets the top clip */
public void setClipTop(int clip) {
mClipRect.top = clip;
setClipBounds(mClipRect);
}
/** Gets the top clip */
public int getClipTop() {
return mClipRect.top;
}
/** Sets the bottom clip */
public void setClipBottom(int clip) {
mClipRect.bottom = clip;
setClipBounds(mClipRect);
}
/** Gets the top clip */
public int getClipBottom() {
return mClipRect.bottom;
}
/** Returns the current dim. */
public void setDim(int dim) {
mDim = dim;
/*
int inverse = 255 - mDim;
mDimColorFilter.setColor(Color.argb(0xFF, inverse, inverse, inverse));
mLayerPaint.setColorFilter(mDimColorFilter);
setLayerType(LAYER_TYPE_HARDWARE, mLayerPaint);
*/
}
/** Returns the current dim. */
public int getDim() {
return mDim;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mClipRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
/** Prepares the screenshot view for the transition into Recents */
public void prepareAnimateOnEnterRecents(Bitmap screenshot) {
if (!mConfig.launchedFromAppWithScreenshot) return;
setClipTop(0);
setClipBottom(getMeasuredHeight());
setDim(0);
setTranslationY(0f);
setScaleX(1f);
setScaleY(1f);
setVisibility(mConfig.launchedFromAppWithScreenshot ? View.VISIBLE : View.INVISIBLE);
if (screenshot != null) {
mScreenshotView.setImageBitmap(screenshot);
} else {
mScreenshotView.setImageDrawable(null);
}
}
/** Resets the transition view */
public void reset() {
setVisibility(View.GONE);
mScreenshotView.setImageDrawable(null);
}
/** Animates this view as it enters recents */
public void animateOnEnterRecents(ViewAnimation.TaskViewEnterContext ctx,
final Runnable postAnimRunnable) {
// Cancel the current animation
if (mEnterAnimation != null) {
mEnterAnimation.removeAllListeners();
mEnterAnimation.cancel();
}
// Calculate the bottom clip
Rect targetTaskRect = ctx.targetTaskTransform.rect;
float scale = (float) targetTaskRect.width() / getMeasuredWidth();
float scaleYOffset = ((1f - scale) * getMeasuredHeight()) / 2;
float scaledTopInset = (int) (scale * mConfig.systemInsets.top);
int translationY = (int) -scaleYOffset + (int) (mConfig.systemInsets.top - scaledTopInset)
+ targetTaskRect.top;
int clipBottom = mConfig.systemInsets.top + (int) (targetTaskRect.height() / scale);
// Calculate the dim
float minScale = TaskStackViewLayoutAlgorithm.StackPeekMinScale;
float scaleRange = 1f - minScale;
float dim = (1f - ctx.targetTaskTransform.scale) / scaleRange;
dim = mDimInterpolator.getInterpolation(Math.min(dim, 1f));
int toDim = Math.max(0, Math.min(mMaxDim, (int) (dim * 255)));
// Enable the HW Layers on the screenshot view
mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
// Compose the animation
mEnterAnimation = new AnimatorSet();
mEnterAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
post(new Runnable() {
@Override
public void run() {
// Mark that we are no longer animating
mIsAnimating = false;
// Disable the HW Layers on this view
setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
// Notify any callbacks
mCb.onEnterAnimationComplete();
// Run the given post-anim runnable
postAnimRunnable.run();
}
});
}
});
// XXX: Translation y should be negative initially to simulate moving from the top of the screen?
mEnterAnimation.setStartDelay(0);
mEnterAnimation.setDuration(475);
mEnterAnimation.setInterpolator(mConfig.fastOutSlowInInterpolator);
mEnterAnimation.playTogether(
// ObjectAnimator.ofInt(this, "clipTop", mConfig.systemInsets.top),
ObjectAnimator.ofInt(this, "clipBottom", clipBottom),
ObjectAnimator.ofInt(this, "dim", toDim),
ObjectAnimator.ofFloat(this, "translationY", translationY),
ObjectAnimator.ofFloat(this, "scaleX", scale),
ObjectAnimator.ofFloat(this, "scaleY", scale)
);
setClipTop(mConfig.systemInsets.top);
mEnterAnimation.start();
mIsAnimating = true;
}
/** Animates this view back out of Recents if we were in the process of animating in. */
public boolean cancelAnimateOnEnterRecents(final Runnable postAnimRunnable) {
if (mIsAnimating) {
// Cancel the current animation
if (mEnterAnimation != null) {
mEnterAnimation.removeAllListeners();
mEnterAnimation.cancel();
}
// Enable the HW Layers on the screenshot view
mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
// Compose the animation
mEnterAnimation = new AnimatorSet();
mEnterAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
post(new Runnable() {
@Override
public void run() {
// Mark that we are no longer animating
mIsAnimating = false;
// Disable the HW Layers on the screenshot view
mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
// Notify any callbacks
mCb.onEnterAnimationComplete();
// Run the given post-anim runnable
postAnimRunnable.run();
}
});
}
});
mEnterAnimation.setDuration(475);
mEnterAnimation.setInterpolator(mConfig.fastOutSlowInInterpolator);
mEnterAnimation.playTogether(
ObjectAnimator.ofInt(this, "clipTop", 0),
ObjectAnimator.ofInt(this, "clipBottom", getMeasuredHeight()),
ObjectAnimator.ofInt(this, "dim", 0),
ObjectAnimator.ofFloat(this, "translationY", 0f),
ObjectAnimator.ofFloat(this, "scaleX", 1f),
ObjectAnimator.ofFloat(this, "scaleY", 1f)
);
mEnterAnimation.start();
return true;
}
return false;
}
}

View File

@ -24,7 +24,6 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.net.Uri;
import android.provider.Settings;
@ -40,7 +39,6 @@ import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.RecentsPackageMonitor;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.SpaceNode;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
@ -63,13 +61,10 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
RecentsConfiguration mConfig;
LayoutInflater mInflater;
Paint mDebugModePaint;
DebugOverlayView mDebugOverlay;
// The space partitioning root of this container
SpaceNode mBSP;
// Search bar view
ArrayList<TaskStack> mStacks;
View mSearchBar;
// Recents view callbacks
RecentsViewCallbacks mCb;
public RecentsView(Context context) {
@ -95,10 +90,13 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
mCb = cb;
}
/** Set/get the bsp root node */
public void setBSP(SpaceNode n) {
mBSP = n;
/** Sets the debug overlay */
public void setDebugOverlay(DebugOverlayView overlay) {
mDebugOverlay = overlay;
}
/** Set/get the bsp root node */
public void setTaskStacks(ArrayList<TaskStack> stacks) {
// Remove all TaskStackViews (but leave the search bar)
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
@ -109,21 +107,18 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
}
// Create and add all the stacks for this partition of space.
ArrayList<TaskStack> stacks = mBSP.getStacks();
for (TaskStack stack : stacks) {
mStacks = stacks;
int numStacks = mStacks.size();
for (int i = 0; i < numStacks; i++) {
TaskStack stack = mStacks.get(i);
TaskStackView stackView = new TaskStackView(getContext(), stack);
stackView.setCallbacks(this);
// Enable debug mode drawing
if (mConfig.debugModeEnabled) {
stackView.setDebugOverlay(mDebugOverlay);
}
addView(stackView);
}
// Enable debug mode drawing
if (mConfig.debugModeEnabled) {
mDebugModePaint = new Paint();
mDebugModePaint.setColor(0xFFff0000);
mDebugModePaint.setStyle(Paint.Style.STROKE);
mDebugModePaint.setStrokeWidth(5f);
setWillNotDraw(false);
}
}
/** Launches the focused task from the first stack if possible */
@ -150,8 +145,8 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
return false;
}
/** Launches the first task from the first stack if possible */
public boolean launchFirstTask() {
/** Launches the task that Recents was launched from, if possible */
public boolean launchPreviousTask() {
// Get the first stack view
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
@ -161,20 +156,17 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
TaskStack stack = stackView.mStack;
ArrayList<Task> tasks = stack.getTasks();
// Get the first task in the stack
// Find the launch task in the stack
if (!tasks.isEmpty()) {
Task task = tasks.get(tasks.size() - 1);
TaskView tv = null;
// Try and use the first child task view as the source of the launch animation
if (stackView.getChildCount() > 0) {
TaskView stv = (TaskView) stackView.getChildAt(stackView.getChildCount() - 1);
if (stv.getTask() == task) {
tv = stv;
int taskCount = tasks.size();
for (int j = 0; j < taskCount; j++) {
if (tasks.get(j).isLaunchTarget) {
Task task = tasks.get(j);
TaskView tv = stackView.getChildViewForTask(task);
onTaskViewClicked(stackView, tv, stack, task, false);
return true;
}
}
onTaskViewClicked(stackView, tv, stack, task, false);
return true;
}
}
}
@ -242,35 +234,31 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// Get the search bar bounds and measure the search bar layout
if (mSearchBar != null) {
Rect searchBarSpaceBounds = new Rect();
mConfig.getSearchBarBounds(width, height - mConfig.systemInsets.top, searchBarSpaceBounds);
mConfig.getSearchBarBounds(width, height, mConfig.systemInsets.top, searchBarSpaceBounds);
mSearchBar.measure(
MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY));
}
// We give the full width of the space, not including the right nav bar insets in landscape,
// to the stack view, since we want the tasks to render under the search bar in landscape.
// In addition, we give it the full height, not including the top inset or search bar space,
// since we want the tasks to render under the navigation buttons in portrait.
Rect taskStackBounds = new Rect();
mConfig.getTaskStackBounds(width, height, taskStackBounds);
int childWidth = width - mConfig.systemInsets.right;
int childHeight = taskStackBounds.height() - mConfig.systemInsets.top;
mConfig.getTaskStackBounds(width, height, mConfig.systemInsets.top,
mConfig.systemInsets.right, taskStackBounds);
// Measure each TaskStackView
// Measure each TaskStackView with the full width and height of the window since the
// transition view is a child of that stack view
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child != mSearchBar && child.getVisibility() != GONE) {
child.measure(MeasureSpec.makeMeasureSpec(childWidth, widthMode),
MeasureSpec.makeMeasureSpec(childHeight, heightMode));
TaskStackView tsv = (TaskStackView) child;
// Set the insets to be the top/left inset + search bounds
tsv.setStackInsetRect(taskStackBounds);
tsv.measure(widthMeasureSpec, heightMeasureSpec);
}
}
@ -285,49 +273,30 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
// Get the search bar bounds so that we lay it out
if (mSearchBar != null) {
Rect searchBarSpaceBounds = new Rect();
mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), searchBarSpaceBounds);
mSearchBar.layout(mConfig.systemInsets.left + searchBarSpaceBounds.left,
mConfig.systemInsets.top + searchBarSpaceBounds.top,
mConfig.systemInsets.left + mSearchBar.getMeasuredWidth(),
mConfig.systemInsets.top + mSearchBar.getMeasuredHeight());
mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(),
mConfig.systemInsets.top, searchBarSpaceBounds);
mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top,
searchBarSpaceBounds.right, searchBarSpaceBounds.bottom);
}
// We offset the stack view by the left inset (if any), but lay it out under the search bar.
// In addition, we offset our stack views by the top inset and search bar height, but not
// the bottom insets because we want it to render under the navigation buttons.
Rect taskStackBounds = new Rect();
mConfig.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds);
left += mConfig.systemInsets.left;
top += mConfig.systemInsets.top + taskStackBounds.top;
// Layout each child
// XXX: Based on the space node for that task view
// Layout each TaskStackView with the full width and height of the window since the
// transition view is a child of that stack view
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child != mSearchBar && child.getVisibility() != GONE) {
TaskStackView tsv = (TaskStackView) child;
child.layout(left, top, left + tsv.getMeasuredWidth(), top + tsv.getMeasuredHeight());
child.layout(left, top, left + child.getMeasuredWidth(),
top + child.getMeasuredHeight());
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Debug mode drawing
if (mConfig.debugModeEnabled) {
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mDebugModePaint);
}
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
// Update the configuration with the latest system insets and trigger a relayout
mConfig.updateSystemInsets(insets.getSystemWindowInsets());
requestLayout();
return insets.consumeSystemWindowInsets(false, false, false, true);
return insets.consumeSystemWindowInsets();
}
/** Notifies each task view of the user interaction. */
@ -364,11 +333,12 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
/** Unfilters any filtered stacks */
public boolean unfilterFilteredStacks() {
if (mBSP != null) {
if (mStacks != null) {
// Check if there are any filtered stacks and unfilter them before we back out of Recents
boolean stacksUnfiltered = false;
ArrayList<TaskStack> stacks = mBSP.getStacks();
for (TaskStack stack : stacks) {
int numStacks = mStacks.size();
for (int i = 0; i < numStacks; i++) {
TaskStack stack = mStacks.get(i);
if (stack.hasFilteredTasks()) {
stack.unfilterTasks();
stacksUnfiltered = true;

View File

@ -43,8 +43,6 @@ class TaskBarView extends FrameLayout {
RecentsConfiguration mConfig;
Task mTask;
ImageView mDismissButton;
ImageView mApplicationIcon;
TextView mActivityDescription;
@ -52,6 +50,8 @@ class TaskBarView extends FrameLayout {
Drawable mLightDismissDrawable;
Drawable mDarkDismissDrawable;
boolean mIsFullscreen;
Paint mLayerPaint = new Paint();
static Paint sHighlightPaint;
@ -113,11 +113,18 @@ class TaskBarView extends FrameLayout {
@Override
protected void onDraw(Canvas canvas) {
// Draw the highlight at the top edge (but put the bottom edge just out of view)
float offset = mConfig.taskViewHighlightPx / 2f;
float radius = mConfig.taskViewRoundedCornerRadiusPx;
canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
if (!mIsFullscreen) {
// Draw the highlight at the top edge (but put the bottom edge just out of view)
float offset = mConfig.taskViewHighlightPx / 2f;
float radius = mConfig.taskViewRoundedCornerRadiusPx;
canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
}
}
/** Sets whether the current task is full screen or not. */
void setIsFullscreen(boolean isFullscreen) {
mIsFullscreen = isFullscreen;
}
/** Synchronizes this bar view's properties with the task's transform */
@ -149,7 +156,6 @@ class TaskBarView extends FrameLayout {
/** Binds the bar view to the task */
void rebindToTask(Task t) {
mTask = t;
// If an activity icon is defined, then we use that as the primary icon to show in the bar,
// otherwise, we fall back to the application icon
if (t.activityIcon != null) {
@ -170,7 +176,6 @@ class TaskBarView extends FrameLayout {
/** Unbinds the bar view from the task */
void unbindFromTask() {
mTask = null;
mApplicationIcon.setImageDrawable(null);
}

View File

@ -0,0 +1,117 @@
/*
* 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.
*/
package com.android.systemui.recents.views;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.recents.RecentsConfiguration;
/** The task footer view */
public class TaskFooterView extends FrameLayout {
interface TaskFooterViewCallbacks {
public void onTaskFooterHeightChanged(int height, int maxHeight);
}
RecentsConfiguration mConfig;
TaskFooterViewCallbacks mCb;
int mFooterHeight;
int mMaxFooterHeight;
ObjectAnimator mFooterAnimator;
public TaskFooterView(Context context) {
this(context, null);
}
public TaskFooterView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TaskFooterView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public TaskFooterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = RecentsConfiguration.getInstance();
mMaxFooterHeight = mConfig.taskViewLockToAppButtonHeight;
setFooterHeight(getFooterHeight());
}
/** Sets the callbacks for when the footer height changes. */
void setCallbacks(TaskFooterViewCallbacks cb) {
mCb = cb;
mCb.onTaskFooterHeightChanged(mFooterHeight, mMaxFooterHeight);
}
/** Sets the footer height. */
public void setFooterHeight(int footerHeight) {
if (footerHeight != mFooterHeight) {
mFooterHeight = footerHeight;
mCb.onTaskFooterHeightChanged(footerHeight, mMaxFooterHeight);
}
}
/** Gets the footer height. */
public int getFooterHeight() {
return mFooterHeight;
}
/** Animates the footer into and out of view. */
void animateFooterVisibility(final boolean visible, int duration) {
// Return early if there is no footer
if (mMaxFooterHeight <= 0) return;
// Return early if we are already in the final state
if (!visible && getVisibility() != View.VISIBLE) return;
if (visible && getVisibility() == View.VISIBLE) return;
// Cancel the previous animation
if (mFooterAnimator != null) {
mFooterAnimator.removeAllListeners();
mFooterAnimator.cancel();
}
int finalHeight = visible ? mMaxFooterHeight : 0;
if (duration > 0) {
// Make the view visible for the animation
if (visible && getVisibility() != View.VISIBLE) {
setVisibility(View.VISIBLE);
}
mFooterAnimator = ObjectAnimator.ofInt(this, "footerHeight", finalHeight);
mFooterAnimator.setDuration(duration);
mFooterAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
mFooterAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (!visible) {
setVisibility(View.INVISIBLE);
}
}
});
mFooterAnimator.start();
} else {
setFooterHeight(finalHeight);
setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
}
}
}

View File

@ -23,7 +23,6 @@ import android.animation.ValueAnimator;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@ -73,6 +72,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
ViewPool<TaskView, Task> mViewPool;
ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>();
DozeTrigger mUIDozeTrigger;
DebugOverlayView mDebugOverlay;
Rect mTaskStackBounds = new Rect();
// The virtual stack scroll that we use for the card layout
int mStackScroll;
@ -94,8 +95,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int[] mTmpVisibleRange = new int[2];
Rect mTmpRect = new Rect();
Rect mTmpRect2 = new Rect();
TaskViewTransform mTmpTransform = new TaskViewTransform();
HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>();
LayoutInflater mInflater;
// A convenience runnable to return all views to the pool
// XXX: After this is set, we should mark this task stack view as disabled and check that in synchronize model
Runnable mReturnAllViewsToPoolRunnable = new Runnable() {
@Override
public void run() {
@ -162,6 +167,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mCb = cb;
}
/** Sets the debug overlay */
public void setDebugOverlay(DebugOverlayView overlay) {
mDebugOverlay = overlay;
}
/** Requests that the views be synchronized with the model */
void requestSynchronizeStackViewsWithModel() {
requestSynchronizeStackViewsWithModel(0);
@ -179,19 +189,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
/** Returns a mapping of child view to Task. */
HashMap<Task, TaskView> getTaskChildViewMap() {
HashMap<Task, TaskView> taskViewMap = new HashMap<Task, TaskView>();
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskView tv = (TaskView) getChildAt(i);
taskViewMap.put(tv.getTask(), tv);
}
return taskViewMap;
}
/** Finds the child view given a specific task. */
TaskView getChildViewForTask(Task t) {
public TaskView getChildViewForTask(Task t) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskView tv = (TaskView) getChildAt(i);
@ -210,19 +209,23 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/**
* Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
*/
private void updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
ArrayList<Task> tasks,
int stackScroll,
int[] visibleRangeOut,
boolean boundTranslationsToRect) {
// XXX: We should be intelligent about where to look for the visible stack range using the
// XXX: We should be intelligent about wheee to look for the visible stack range using the
// current stack scroll.
// XXX: We should log extra cases like the ones below where we don't expect to hit very often
// XXX: Print out approximately how many indices we have to go through to find the first visible transform
int taskTransformCount = taskTransforms.size();
int taskCount = tasks.size();
int frontMostVisibleIndex = -1;
int backMostVisibleIndex = -1;
// We can reuse the task transforms where possible to reduce object allocation
if (taskTransformCount < taskCount) {
// If there are less transforms than tasks, then add as many transforms as necessary
@ -256,13 +259,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (boundTranslationsToRect) {
transform.translationY = Math.min(transform.translationY,
mStackAlgorithm.mRect.bottom);
mStackAlgorithm.mViewRect.bottom);
}
}
if (visibleRangeOut != null) {
visibleRangeOut[0] = frontMostVisibleIndex;
visibleRangeOut[1] = backMostVisibleIndex;
}
return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1;
}
/**
@ -280,34 +284,33 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/** Synchronizes the views with the model */
void synchronizeStackViewsWithModel() {
boolean synchronizeStackViewsWithModel() {
if (mStackViewsDirty) {
// Get all the task transforms
ArrayList<Task> tasks = mStack.getTasks();
int stackScroll = getStackScroll();
int[] visibleRange = mTmpVisibleRange;
updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false);
TaskViewTransform tmpTransform = new TaskViewTransform();
boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false);
// Return all the invisible children to the pool
HashMap<Task, TaskView> taskChildViewMap = getTaskChildViewMap();
mTmpTaskViewMap.clear();
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
TaskView tv = (TaskView) getChildAt(i);
Task task = tv.getTask();
int taskIndex = mStack.indexOfTask(task);
if (taskIndex < visibleRange[1] || taskIndex > visibleRange[0]) {
taskChildViewMap.remove(task);
if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
mTmpTaskViewMap.put(task, tv);
} else {
mViewPool.returnViewToPool(tv);
}
}
// Pick up all the newly visible children and update all the existing children
boolean isValidVisibleRange = visibleRange[0] != -1 && visibleRange[1] != -1;
for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) {
Task task = tasks.get(i);
TaskViewTransform transform = mCurrentTaskTransforms.get(i);
TaskView tv = taskChildViewMap.get(task);
TaskView tv = mTmpTaskViewMap.get(task);
int taskIndex = mStack.indexOfTask(task);
if (tv == null) {
@ -316,23 +319,26 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// For items in the list, put them in start animating them from the
// approriate ends of the list where they are expected to appear
if (transform.t < 0) {
tmpTransform = mStackAlgorithm.getStackTransform(tasks.get(0), stackScroll, tmpTransform);
mTmpTransform = mStackAlgorithm.getStackTransform(tasks.get(0), stackScroll, mTmpTransform);
} else {
tmpTransform = mStackAlgorithm.getStackTransform(tasks.get(Math.min(tasks.size() - 1, visibleRange[0] + 1)),
stackScroll, tmpTransform);
mTmpTransform = mStackAlgorithm.getStackTransform(tasks.get(Math.min(tasks.size() - 1, visibleRange[0] + 1)),
stackScroll, mTmpTransform);
}
tv.updateViewPropertiesToTaskTransform(tmpTransform, 0);
tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
}
}
// Update and animate the task into place
// Animate the task into place
tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
mStackViewsAnimationDuration);
}
// Reset the request-synchronize params
mStackViewsAnimationDuration = 0;
mStackViewsDirty = false;
return true;
}
return false;
}
/** Updates the clip for each of the task views. */
@ -368,13 +374,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
clipBottom = (mTmpRect.bottom - scaledMaxFooterHeight - mTmpRect2.top);
}
}
tv.setClipFromBottom(clipBottom);
tv.getViewBounds().setClipBottom(clipBottom);
}
if (getChildCount() > 0) {
// The front most task should never be clipped
TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
tv.getViewBounds().setClipBottom(0);
}
}
if (getChildCount() > 0) {
// The front most task should never be clipped
TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
tv.setClipFromBottom(0);
}
}
@ -384,18 +390,24 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskView tv = (TaskView) getChildAt(i);
tv.setClipFromBottom(0);
tv.getViewBounds().setClipBottom(0);
}
}
mEnableStackClipping = stackClippingEnabled;
}
/** The stack insets to apply to the stack contents */
public void setStackInsetRect(Rect r) {
mTaskStackBounds.set(r);
}
/** Sets the current stack scroll */
public void setStackScroll(int value) {
mStackScroll = value;
mUIDozeTrigger.poke();
requestSynchronizeStackViewsWithModel();
}
/** Sets the current stack scroll without synchronizing the stack view with the model */
public void setStackScrollRaw(int value) {
mStackScroll = value;
@ -408,7 +420,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/** Computes the initial stack scroll for the stack. */
int getInitialStackScroll() {
if (mStack.getTaskCount() > 2) {
return mMaxScroll - (int) (mStackAlgorithm.mTaskRect.height() * (3f/4f));
return Math.max(mMinScroll, mMaxScroll - (int) (mStackAlgorithm.mTaskRect.height() * (3f/4f)));
}
return mMaxScroll;
}
@ -521,7 +533,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/** Returns whether the specified scroll is out of bounds */
boolean isScrollOutOfBounds() {
return getScrollAmountOutOfBounds(getStackScroll()) != 0;
return getScrollAmountOutOfBounds(mStackScroll) != 0;
}
/** Updates the min and max virtual scroll bounds */
@ -639,24 +651,24 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void dispatchDraw(Canvas canvas) {
synchronizeStackViewsWithModel();
clipTaskViews();
if (synchronizeStackViewsWithModel()) {
clipTaskViews();
}
super.dispatchDraw(canvas);
}
/** Computes the stack and task rects */
public void computeRects(int width, int height, int insetLeft, int insetBottom) {
public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
// Compute the rects in the stack algorithm
mStackAlgorithm.computeRects(mStack.getTasks(), width, height, insetLeft, insetBottom);
mStackAlgorithm.computeRects(mStack.getTasks(), windowWidth, windowHeight, taskStackBounds);
// Update the scroll bounds
updateMinMaxScroll(false);
}
/**
* This is called with the size of the space not including the top or right insets, or the
* search bar height in portrait (but including the search bar width in landscape, since we want
* to draw under it.
* This is called with the full window width and height to allow stack view children to
* perform the full screen transition down.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
@ -664,25 +676,43 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int height = MeasureSpec.getSize(heightMeasureSpec);
// Compute our stack/task rects
Rect taskStackBounds = new Rect();
mConfig.getTaskStackBounds(width, height, taskStackBounds);
computeRects(width, height, taskStackBounds.left, mConfig.systemInsets.bottom);
Rect taskStackBounds = new Rect(mTaskStackBounds);
taskStackBounds.bottom -= mConfig.systemInsets.bottom;
computeRects(width, height, taskStackBounds);
// If this is the first layout, then scroll to the front of the stack and synchronize the
// stack views immediately
// stack views immediately to load all the views
if (mAwaitingFirstLayout) {
setStackScrollToInitialState();
requestSynchronizeStackViewsWithModel();
synchronizeStackViewsWithModel();
// Find the first task and mark it as full screen
if (mConfig.launchedFromAppWithScreenshot) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskView tv = (TaskView) getChildAt(i);
if (tv.getTask().isLaunchTarget) {
tv.setIsFullScreen(true);
break;
}
}
}
}
// Measure each of the children
// Measure each of the TaskViews
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskView t = (TaskView) getChildAt(i);
t.measure(MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), MeasureSpec.EXACTLY),
TaskView tv = (TaskView) getChildAt(i);
if (tv.isFullScreenView()) {
tv.measure(widthMeasureSpec, heightMeasureSpec);
} else {
tv.measure(
MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(),
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height() +
t.getMaxFooterHeight(), MeasureSpec.EXACTLY));
tv.getMaxFooterHeight(), MeasureSpec.EXACTLY));
}
}
setMeasuredDimension(width, height);
@ -698,54 +728,45 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Layout each of the children
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskView t = (TaskView) getChildAt(i);
t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
mStackAlgorithm.mTaskRect.height() + t.getMaxFooterHeight());
TaskView tv = (TaskView) getChildAt(i);
if (tv.isFullScreenView()) {
tv.layout(left, top, left + tv.getMeasuredWidth(), top + tv.getMeasuredHeight());
} else {
tv.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mTaskRect.top,
mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mTaskRect.bottom +
tv.getMaxFooterHeight());
}
}
if (mAwaitingFirstLayout) {
// Mark that we have completely the first layout
mAwaitingFirstLayout = false;
onFirstLayout();
}
}
// Find the target task with the specified id
ArrayList<Task> tasks = mStack.getTasks();
Task targetTask = null;
int targetTaskId = mConfig.launchedToTaskId;
if (targetTaskId != -1) {
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
Task t = tasks.get(i);
if (t.key.id == targetTaskId) {
targetTask = t;
break;
}
}
}
/** Handler for the first layout. */
void onFirstLayout() {
// Prepare the first view for its enter animation
int offscreenY = mStackAlgorithm.mViewRect.bottom -
(mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mViewRect.top);
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
TaskView tv = (TaskView) getChildAt(i);
tv.prepareEnterRecentsAnimation(tv.getTask().isLaunchTarget, offscreenY);
}
// Prepare the first view for its enter animation
int offsetTopAlign = -mStackAlgorithm.mTaskRect.top;
int offscreenY = mStackAlgorithm.mRect.bottom -
(mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top);
for (int i = childCount - 1; i >= 0; i--) {
TaskView tv = (TaskView) getChildAt(i);
tv.prepareEnterRecentsAnimation(tv.getTask() == targetTask, offsetTopAlign,
offscreenY);
}
// If the enter animation started already and we haven't completed a layout yet, do the
// enter animation now
if (mStartEnterAnimationRequestedAfterLayout) {
startEnterRecentsAnimation(mStartEnterAnimationContext);
mStartEnterAnimationRequestedAfterLayout = false;
mStartEnterAnimationContext = null;
}
// If the enter animation started already and we haven't completed a layout yet, do the
// enter animation now
if (mStartEnterAnimationRequestedAfterLayout) {
startEnterRecentsAnimation(mStartEnterAnimationContext);
mStartEnterAnimationRequestedAfterLayout = false;
mStartEnterAnimationContext = null;
}
// Update the focused task index to be the next item to the top task
if (mConfig.launchedWithAltTab) {
// When alt-tabbing, we focus the next previous task
focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
}
// Update the focused task index to be the next item to the top task
if (mConfig.launchedWithAltTab) {
// When alt-tabbing, we focus the next previous task
focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
}
}
@ -759,30 +780,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
if (mStack.getTaskCount() > 0) {
// Find the target task with the specified id
ArrayList<Task> tasks = mStack.getTasks();
Task targetTask = null;
int targetTaskId = mConfig.launchedToTaskId;
if (targetTaskId != -1) {
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
Task t = tasks.get(i);
if (t.key.id == targetTaskId) {
targetTask = t;
break;
}
}
}
// Find the transform for the target task
if (targetTask != null) {
ctx.targetTaskTransform = new TaskViewTransform();
mStackAlgorithm.getStackTransform(targetTask, getStackScroll(), ctx.targetTaskTransform);
Rect taskStackBounds = new Rect();
mConfig.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds);
ctx.targetTaskTransform.rect.offset(taskStackBounds.left, taskStackBounds.top);
}
// Animate all the task views into view
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
@ -791,7 +788,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
ctx.currentTaskTransform = new TaskViewTransform();
ctx.currentStackViewIndex = i;
ctx.currentStackViewCount = childCount;
ctx.isCurrentTaskLaunchTarget = (task == targetTask);
ctx.currentTaskRect = mStackAlgorithm.mTaskRect;
mStackAlgorithm.getStackTransform(task, getStackScroll(), ctx.currentTaskTransform);
tv.startEnterRecentsAnimation(ctx);
}
@ -802,6 +799,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public void run() {
// Start dozing
mUIDozeTrigger.startDozing();
// Request an update of the task views after the animation in to
// relayout the fullscreen view back to its normal size
if (mConfig.launchedFromAppWithScreenshot) {
requestSynchronizeStackViewsWithModel();
}
}
});
}
@ -810,8 +812,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/** Requests this task stacks to start it's exit-recents animation. */
public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
// Animate all the task views into view
ctx.offscreenTranslationY = mStackAlgorithm.mRect.bottom -
(mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top);
ctx.offscreenTranslationY = mStackAlgorithm.mViewRect.bottom -
(mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mViewRect.top);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskView tv = (TaskView) getChildAt(i);

View File

@ -17,7 +17,6 @@
package com.android.systemui.recents.views;
import android.graphics.Rect;
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;
@ -30,14 +29,14 @@ public class TaskStackViewLayoutAlgorithm {
// These are all going to change
static final float StackOverlapPct = 0.65f; // The overlap height relative to the task height
static final float StackPeekHeightPct = 0.1f; // The height of the peek space relative to the stack height
static final float StackPeekHeightPct = 0.075f; // The height of the peek space relative to the stack height
static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area
static final int StackPeekNumCards = 3; // The number of cards we see in the peek space
RecentsConfiguration mConfig;
// The various rects that define the stack view
Rect mRect = new Rect();
Rect mViewRect = new Rect();
Rect mStackRect = new Rect();
Rect mStackRectSansPeek = new Rect();
Rect mTaskRect = new Rect();
@ -53,29 +52,21 @@ public class TaskStackViewLayoutAlgorithm {
}
/** Computes the stack and task rects */
public void computeRects(ArrayList<Task> tasks, int width, int height, int insetLeft, int insetBottom) {
public void computeRects(ArrayList<Task> tasks, int windowWidth, int windowHeight,
Rect taskStackBounds) {
// Note: We let the stack view be the full height because we want the cards to go under the
// navigation bar if possible. However, the stack rects which we use to calculate
// max scroll, etc. need to take the nav bar into account
// Compute the stack rects
mRect.set(0, 0, width, height);
mStackRect.set(mRect);
mStackRect.left += insetLeft;
mStackRect.bottom -= insetBottom;
mViewRect.set(0, 0, windowWidth, windowHeight);
mStackRect.set(taskStackBounds);
int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
int heightPadding = mConfig.taskStackTopPaddingPx;
if (Constants.DebugFlags.App.EnableSearchLayout) {
mStackRect.top += heightPadding;
mStackRect.left += widthPadding;
mStackRect.right -= widthPadding;
mStackRect.bottom -= heightPadding;
} else {
mStackRect.inset(widthPadding, heightPadding);
}
mStackRect.inset(widthPadding, heightPadding);
mStackRectSansPeek.set(mStackRect);
mStackRectSansPeek.top += StackPeekHeightPct * mStackRect.height();
mStackRectSansPeek.top += StackPeekHeightPct * windowHeight;
// Compute the task rect
int size = mStackRect.width();
@ -91,7 +82,7 @@ public class TaskStackViewLayoutAlgorithm {
// Compute the min and max scroll values
int numTasks = Math.max(1, tasks.size());
int taskHeight = mTaskRect.height();
int stackHeight = mStackRectSansPeek.height();
int stackHeight = mStackRect.height();
if (numTasks <= 1) {
// If there is only one task, then center the task in the stack rect (sans peek)
@ -156,7 +147,7 @@ public class TaskStackViewLayoutAlgorithm {
} else {
transformOut.rect.offset(0, transformOut.translationY);
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = Rect.intersects(mRect, transformOut.rect);
transformOut.visible = Rect.intersects(mViewRect, transformOut.rect);
}
transformOut.t = t;
return transformOut;

View File

@ -17,7 +17,7 @@
package com.android.systemui.recents.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
@ -27,11 +27,8 @@ import com.android.systemui.recents.model.Task;
/** The task thumbnail view */
public class TaskThumbnailView extends FixedSizeImageView {
Task mTask;
// Task bar clipping
Rect mClipRect;
boolean mClipTaskBar = true;
Rect mClipRect = new Rect();
public TaskThumbnailView(Context context) {
this(context, null);
@ -50,40 +47,51 @@ public class TaskThumbnailView extends FixedSizeImageView {
setScaleType(ScaleType.FIT_XY);
}
@Override
public void draw(Canvas canvas) {
if (mClipTaskBar && (mClipRect != null)) {
int restoreCount = canvas.save(Canvas.CLIP_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
canvas.clipRect(mClipRect);
super.draw(canvas);
canvas.restoreToCount(restoreCount);
} else {
super.draw(canvas);
}
/** Updates the clip rect based on the given task bar. */
void enableTaskBarClip(View taskBar) {
int top = (int) Math.max(0, taskBar.getTranslationY() +
taskBar.getMeasuredHeight() - 1);
mClipRect.set(0, top, getMeasuredWidth(), getMeasuredHeight());
setClipBounds(mClipRect);
}
/** Updates the clip rect based on the given task bar. */
void updateTaskBarClip(View taskBar) {
// If mClipTaskBar is unset first, then we don't bother setting mTaskBar
if (mClipTaskBar) {
int top = (int) Math.max(0, taskBar.getTranslationY() +
taskBar.getMeasuredHeight() - 1);
mClipRect = new Rect(0, top, getMeasuredWidth(), getMeasuredHeight());
invalidate(0, 0, taskBar.getMeasuredWidth(), taskBar.getMeasuredHeight() + 1);
}
/** Convenience method to enable task bar clipping as a runnable. */
Runnable enableTaskBarClipAsRunnable(final View taskBar) {
return new Runnable() {
@Override
public void run() {
enableTaskBarClip(taskBar);
}
};
}
/** Disables the task bar clipping. */
void disableClipTaskBarView() {
mClipTaskBar = false;
if (mClipRect != null) {
invalidate(0, 0, mClipRect.width(), mClipRect.top);
Runnable disableTaskBarClipAsRunnable() {
return new Runnable() {
@Override
public void run() {
mClipRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
setClipBounds(mClipRect);
}
};
}
/** Binds the thumbnail view to the screenshot. */
boolean bindToScreenshot(Bitmap ss) {
if (ss != null) {
setImageBitmap(ss);
return true;
}
return false;
}
/** Unbinds the thumbnail view from the screenshot. */
void unbindFromScreenshot() {
setImageBitmap(null);
}
/** Binds the thumbnail view to the task */
void rebindToTask(Task t) {
mTask = t;
if (t.thumbnail != null) {
setImageBitmap(t.thumbnail);
}
@ -91,7 +99,6 @@ public class TaskThumbnailView extends FixedSizeImageView {
/** Unbinds the thumbnail view from the task */
void unbindFromTask() {
mTask = null;
setImageDrawable(null);
}
}

View File

@ -22,24 +22,23 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.ViewPropertyAnimator;
import android.view.animation.AccelerateInterpolator;
import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.recents.AlternateRecentsComponent;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.model.Task;
// XXX: In debug mode, we should override invalidate() and check the layout type (do this in TaskStackView as well)
/* A task view */
public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.OnClickListener,
View.OnLongClickListener {
public class TaskView extends FrameLayout implements Task.TaskCallbacks,
TaskFooterView.TaskFooterViewCallbacks, View.OnClickListener, View.OnLongClickListener {
/** The TaskView callbacks */
interface TaskViewCallbacks {
public void onTaskViewAppIconClicked(TaskView tv);
@ -51,10 +50,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
RecentsConfiguration mConfig;
int mFooterHeight;
int mMaxFooterHeight;
ObjectAnimator mFooterAnimator;
int mDim;
int mMaxDim;
AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator();
@ -62,14 +57,15 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
Task mTask;
boolean mTaskDataLoaded;
boolean mIsFocused;
boolean mIsFullScreenView;
boolean mIsStub;
boolean mClipViewInStack;
int mClipFromBottom;
AnimateableViewBounds mViewBounds;
Paint mLayerPaint = new Paint();
TaskThumbnailView mThumbnailView;
TaskBarView mBarView;
View mLockToAppButtonView;
TaskFooterView mFooterView;
TaskViewCallbacks mCb;
// Optimizations
@ -80,18 +76,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
updateDimOverlayFromScale();
}
};
Runnable mEnableThumbnailClip = new Runnable() {
@Override
public void run() {
mThumbnailView.updateTaskBarClip(mBarView);
}
};
Runnable mDisableThumbnailClip = new Runnable() {
@Override
public void run() {
mThumbnailView.disableClipTaskBarView();
}
};
public TaskView(Context context) {
@ -109,55 +93,12 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = RecentsConfiguration.getInstance();
mMaxFooterHeight = mConfig.taskViewLockToAppButtonHeight;
setWillNotDraw(false);
setClipToOutline(true);
setDim(getDim());
setFooterHeight(getFooterHeight());
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
// The current height is measured with the footer, so account for the footer height
// and the current clip (in the stack)
int height = getMeasuredHeight() - mClipFromBottom - mMaxFooterHeight + mFooterHeight;
outline.setRoundRect(0, 0, getWidth(), height,
mConfig.taskViewRoundedCornerRadiusPx);
}
});
}
@Override
protected void onFinishInflate() {
mMaxDim = mConfig.taskStackMaxDim;
// By default, all views are clipped to other views in their stack
mClipViewInStack = true;
// Bind the views
mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
mLockToAppButtonView = findViewById(R.id.lock_to_app);
if (mTaskDataLoaded) {
onTaskDataLoaded();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
// Measure the bar view, thumbnail, and lock-to-app buttons
mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
mLockToAppButtonView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight,
MeasureSpec.EXACTLY));
// Measure the thumbnail height to be the same as the width
mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
setMeasuredDimension(width, height);
mViewBounds = new AnimateableViewBounds(this, mConfig.taskViewRoundedCornerRadiusPx);
setWillNotDraw(false);
setDim(getDim());
setOutlineProvider(mViewBounds);
}
/** Set callback */
@ -170,74 +111,67 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
return mTask;
}
/** Returns the view bounds. */
AnimateableViewBounds getViewBounds() {
return mViewBounds;
}
@Override
protected void onFinishInflate() {
// Bind the views
mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
mFooterView = (TaskFooterView) findViewById(R.id.lock_to_app);
mFooterView.setCallbacks(this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
// Measure the bar view, thumbnail, and footer
mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
mFooterView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight,
MeasureSpec.EXACTLY));
if (mIsFullScreenView) {
// Measure the thumbnail height to be the full dimensions
mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
} else {
// Measure the thumbnail to be square
mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
}
setMeasuredDimension(width, height);
}
/** Synchronizes this view's properties with the task's transform */
void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) {
// Update the bar view
mBarView.updateViewPropertiesToTaskTransform(toTransform, duration);
// Check to see if any properties have changed, and update the task view
if (duration > 0) {
ViewPropertyAnimator anim = animate();
boolean useLayers = false;
// Animate to the final state
if (toTransform.hasTranslationYChangedFrom(getTranslationY())) {
anim.translationY(toTransform.translationY);
}
if (Constants.DebugFlags.App.EnableShadows &&
toTransform.hasTranslationZChangedFrom(getTranslationZ())) {
anim.translationZ(toTransform.translationZ);
}
if (toTransform.hasScaleChangedFrom(getScaleX())) {
anim.scaleX(toTransform.scale)
.scaleY(toTransform.scale)
.setUpdateListener(mUpdateDimListener);
useLayers = true;
}
if (toTransform.hasAlphaChangedFrom(getAlpha())) {
// Use layers if we animate alpha
anim.alpha(toTransform.alpha);
useLayers = true;
}
if (useLayers) {
anim.withLayer();
}
anim.setStartDelay(toTransform.startDelay)
.setDuration(duration)
.setInterpolator(mConfig.fastOutSlowInInterpolator)
.start();
} else {
// Set the changed properties
if (toTransform.hasTranslationYChangedFrom(getTranslationY())) {
setTranslationY(toTransform.translationY);
}
// If we are a full screen view, then only update the Z to keep it in order
// XXX: Also update/animate the dim as well
if (mIsFullScreenView) {
if (Constants.DebugFlags.App.EnableShadows &&
toTransform.hasTranslationZChangedFrom(getTranslationZ())) {
setTranslationZ(toTransform.translationZ);
}
if (toTransform.hasScaleChangedFrom(getScaleX())) {
setScaleX(toTransform.scale);
setScaleY(toTransform.scale);
updateDimOverlayFromScale();
}
if (toTransform.hasAlphaChangedFrom(getAlpha())) {
setAlpha(toTransform.alpha);
}
return;
}
// Apply the transform
toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator,
mUpdateDimListener);
}
/** Resets this view's properties */
void resetViewProperties() {
setTranslationX(0f);
setTranslationY(0f);
if (Constants.DebugFlags.App.EnableShadows) {
setTranslationZ(0f);
}
setScaleX(1f);
setScaleY(1f);
setAlpha(1f);
setDim(0);
invalidate();
TaskViewTransform.reset(this);
}
/**
@ -262,20 +196,13 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
/** 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, int offsetY,
int offscreenY) {
public void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, int offscreenY) {
if (mConfig.launchedFromAppWithScreenshot) {
if (isTaskViewLaunchTargetTask) {
// Hide the task view as we are going to animate the full screenshot into view
// and then replace it with this view once we are done
setVisibility(View.INVISIBLE);
// Also hide the front most task bar view so we can animate it in
mBarView.prepareEnterRecentsAnimation();
} else {
// Top align the task views
setTranslationY(offsetY);
setScaleX(1f);
setScaleY(1f);
// Don't do anything for the side views
}
} else if (mConfig.launchedFromAppWithThumbnail) {
@ -300,49 +227,57 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
/** Animates this task view as it enters recents */
public void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
TaskViewTransform transform = ctx.currentTaskTransform;
Rect taskRect = ctx.currentTaskRect;
if (mConfig.launchedFromAppWithScreenshot) {
if (ctx.isCurrentTaskLaunchTarget) {
// Animate the full screenshot down first, before swapping with this task view
ctx.fullScreenshotView.animateOnEnterRecents(ctx, new Runnable() {
@Override
public void run() {
// Animate the task bar of the first task view
mBarView.startEnterRecentsAnimation(0, mEnableThumbnailClip);
setVisibility(View.VISIBLE);
// Animate the footer into view
animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
// Decrement the post animation trigger
ctx.postAnimationTrigger.decrement();
}
});
} else {
// Animate the tasks down behind the full screenshot
animate()
.scaleX(transform.scale)
.scaleY(transform.scale)
.translationY(transform.translationY)
.setStartDelay(0)
.setUpdateListener(null)
.setInterpolator(mConfig.linearOutSlowInInterpolator)
.setDuration(475)
.withLayer()
if (mTask.isLaunchTarget) {
// XXX: We would have to animate the trasnlationY of the task view bar along with the clip and
// reset it at the bottom
// XXX: This should actually be the inset on the current app...
mViewBounds.animateClipTop(taskRect.top, mConfig.taskViewEnterFromHomeDuration * 5);
mViewBounds.animateClipBottom(getMeasuredHeight() - taskRect.bottom, mConfig.taskViewEnterFromHomeDuration * 5);
animate().scaleX(((float) taskRect.width() / getMeasuredWidth()) * transform.scale)
.scaleY(((float) taskRect.width() / getMeasuredWidth()) * transform.scale)
.translationY(taskRect.top + transform.translationY)
.setDuration(mConfig.taskViewEnterFromHomeDuration * 5)
.withEndAction(new Runnable() {
@Override
public void run() {
mEnableThumbnailClip.run();
// Animate the task bar of the first task view
mBarView.startEnterRecentsAnimation(0, mThumbnailView.enableTaskBarClipAsRunnable(mBarView));
// Animate the footer into view (if it is the front most task)
animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
// Decrement the post animation trigger
ctx.postAnimationTrigger.decrement();
// XXX Request layout and only start hte next animation after the next
// layout
setIsFullScreen(false);
mThumbnailView.unbindFromScreenshot();
// Recycle the full screen screenshot
AlternateRecentsComponent.consumeLastScreenshot();
}
})
.withLayer()
.start();
} else {
// Otherwise, just enable the thumbnail clip
mThumbnailView.enableTaskBarClip(mBarView);
// Animate the footer into view
animateFooterVisibility(true, 0);
}
ctx.postAnimationTrigger.increment();
} else if (mConfig.launchedFromAppWithThumbnail) {
if (ctx.isCurrentTaskLaunchTarget) {
if (mTask.isLaunchTarget) {
// Animate the task bar of the first task view
mBarView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay, mEnableThumbnailClip);
mBarView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay,
mThumbnailView.enableTaskBarClipAsRunnable(mBarView));
// Animate the dim into view as well
ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", getDimOverlayFromScale());
@ -360,10 +295,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
ctx.postAnimationTrigger.increment();
// Animate the footer into view
animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration
);
animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
} else {
mEnableThumbnailClip.run();
mThumbnailView.enableTaskBarClip(mBarView);
}
} else if (mConfig.launchedFromHome) {
@ -386,7 +320,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
.withEndAction(new Runnable() {
@Override
public void run() {
mEnableThumbnailClip.run();
mThumbnailView.enableTaskBarClip(mBarView);
// Decrement the post animation trigger
ctx.postAnimationTrigger.decrement();
}
@ -395,11 +329,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
ctx.postAnimationTrigger.increment();
// Animate the footer into view
animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration
);
animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration);
} else {
// Otherwise, just enable the thumbnail clip
mEnableThumbnailClip.run();
mThumbnailView.enableTaskBarClip(mBarView);
// Animate the footer into view
animateFooterVisibility(true, 0);
@ -424,7 +358,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
public void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask) {
if (isLaunchingTask) {
// Disable the thumbnail clip and animate the bar out
mBarView.startLaunchTaskAnimation(mDisableThumbnailClip, r);
mBarView.startLaunchTaskAnimation(mThumbnailView.disableTaskBarClipAsRunnable(), r);
// Animate the dim
if (mDim > 0) {
@ -478,32 +412,23 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
mBarView.setNoUserInteractionState();
}
/** Enable the hw layers on this task view */
void enableHwLayers() {
mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
mBarView.enableHwLayers();
mLockToAppButtonView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
/** Sets whether this task view is full screen or not. */
void setIsFullScreen(boolean isFullscreen) {
mIsFullScreenView = isFullscreen;
mBarView.setIsFullscreen(isFullscreen);
if (isFullscreen) {
// If we are full screen, then disable the bottom outline clip for the footer
mViewBounds.setOutlineClipBottom(0);
}
}
/** Disable the hw layers on this task view */
void disableHwLayers() {
mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
mBarView.disableHwLayers();
mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
/** Returns whether this task view should currently be drawn as a full screen view. */
boolean isFullScreenView() {
return mIsFullScreenView;
}
/** Sets the stubbed state of this task view. */
void setStubState(boolean isStub) {
if (!mIsStub && isStub) {
// This is now a stub task view, so clip to the bar height, hide the thumbnail
setClipBounds(new Rect(0, 0, getMeasuredWidth(), mBarView.getMeasuredHeight()));
mThumbnailView.setVisibility(View.INVISIBLE);
// Temporary
mBarView.mActivityDescription.setText("Stub");
} else if (mIsStub && !isStub) {
setClipBounds(null);
mThumbnailView.setVisibility(View.VISIBLE);
}
mIsStub = isStub;
}
@ -512,7 +437,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
* view.
*/
boolean shouldClipViewInStack() {
return mClipViewInStack && (getVisibility() == View.VISIBLE);
return mClipViewInStack && !mIsFullScreenView && (getVisibility() == View.VISIBLE);
}
/** Sets whether this view should be clipped, or clipped against. */
@ -523,75 +448,19 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
}
}
void setClipFromBottom(int clipFromBottom) {
clipFromBottom = Math.max(0, Math.min(getMeasuredHeight(), clipFromBottom));
if (mClipFromBottom != clipFromBottom) {
mClipFromBottom = clipFromBottom;
invalidateOutline();
}
}
/** Sets the footer height. */
public void setFooterHeight(int footerHeight) {
if (footerHeight != mFooterHeight) {
mFooterHeight = footerHeight;
invalidateOutline();
invalidate(0, getMeasuredHeight() - mMaxFooterHeight, getMeasuredWidth(),
getMeasuredHeight());
}
}
/** Gets the footer height. */
public int getFooterHeight() {
return mFooterHeight;
}
/** Gets the max footer height. */
public int getMaxFooterHeight() {
return mMaxFooterHeight;
return mFooterView.mMaxFooterHeight;
}
/** Animates the footer into and out of view. */
public void animateFooterVisibility(boolean visible, int duration) {
if (!mTask.lockToThisTask) {
if (mLockToAppButtonView.getVisibility() == View.VISIBLE) {
mLockToAppButtonView.setVisibility(View.INVISIBLE);
}
return;
}
if (mMaxFooterHeight <= 0) return;
if (mFooterAnimator != null) {
mFooterAnimator.removeAllListeners();
mFooterAnimator.cancel();
}
int height = visible ? mMaxFooterHeight : 0;
if (visible && mLockToAppButtonView.getVisibility() != View.VISIBLE) {
if (duration > 0) {
setFooterHeight(0);
} else {
setFooterHeight(mMaxFooterHeight);
}
mLockToAppButtonView.setVisibility(View.VISIBLE);
}
if (duration > 0) {
mFooterAnimator = ObjectAnimator.ofInt(this, "footerHeight", height);
mFooterAnimator.setDuration(duration);
mFooterAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
if (!visible) {
mFooterAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLockToAppButtonView.setVisibility(View.INVISIBLE);
}
});
}
mFooterAnimator.start();
} else {
if (!visible) {
mLockToAppButtonView.setVisibility(View.INVISIBLE);
}
}
void animateFooterVisibility(boolean visible, int duration) {
// Hide the footer if we are a full screen view
if (mIsFullScreenView) return;
// Hide the footer if the current task can not be locked to
if (!mTask.lockToTaskEnabled || !mTask.lockToThisTask) return;
// Otherwise, animate the visibility
mFooterView.animateFooterVisibility(visible, duration);
}
/** Returns the current dim. */
@ -619,6 +488,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
setDim(getDimOverlayFromScale());
}
/**** View drawing ****/
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
@ -631,13 +502,29 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (mIsStub && (child == mThumbnailView)) {
if (mIsStub && (child != mBarView)) {
// Skip the thumbnail view if we are in stub mode
return false;
}
return super.drawChild(canvas, child, drawingTime);
}
/** Enable the hw layers on this task view */
void enableHwLayers() {
mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
mBarView.enableHwLayers();
mFooterView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
}
/** Disable the hw layers on this task view */
void disableHwLayers() {
mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
mBarView.disableHwLayers();
mFooterView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
}
/**** View focus state ****/
/**
* Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
* if the view is not currently visible, or we are in touch state (where we still want to keep
@ -691,14 +578,18 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
public void onTaskDataLoaded() {
if (mThumbnailView != null && mBarView != null) {
// Bind each of the views to the new task data
mThumbnailView.rebindToTask(mTask);
if (mIsFullScreenView) {
mThumbnailView.bindToScreenshot(AlternateRecentsComponent.getLastScreenshot());
} else {
mThumbnailView.rebindToTask(mTask);
}
mBarView.rebindToTask(mTask);
// Rebind any listeners
if (Constants.DebugFlags.App.EnableTaskFiltering) {
mBarView.mApplicationIcon.setOnClickListener(this);
}
mBarView.mDismissButton.setOnClickListener(this);
mLockToAppButtonView.setOnClickListener(this);
mFooterView.setOnClickListener(this);
if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
if (mConfig.developerOptionsEnabled) {
mBarView.mApplicationIcon.setOnLongClickListener(this);
@ -720,7 +611,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
mBarView.mApplicationIcon.setOnClickListener(null);
}
mBarView.mDismissButton.setOnClickListener(null);
mLockToAppButtonView.setOnClickListener(null);
mFooterView.setOnClickListener(null);
if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
mBarView.mApplicationIcon.setOnLongClickListener(null);
}
@ -733,6 +624,21 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
setOnClickListener(enabled ? this : null);
}
/**** TaskFooterView.TaskFooterViewCallbacks ****/
@Override
public void onTaskFooterHeightChanged(int height, int maxHeight) {
if (mIsFullScreenView) {
// Disable the bottom outline clip when fullscreen
mViewBounds.setOutlineClipBottom(0);
} else {
// Update the bottom clip in our outline provider
mViewBounds.setOutlineClipBottom(maxHeight - height);
}
}
/**** View.OnClickListener Implementation ****/
@Override
public void onClick(final View v) {
// We purposely post the handler delayed to allow for the touch feedback to draw
@ -752,13 +658,15 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On
});
// Hide the footer
tv.animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration);
} else if (v == tv || v == mLockToAppButtonView) {
mCb.onTaskViewClicked(tv, tv.getTask(), (v == mLockToAppButtonView));
} else if (v == tv || v == mFooterView) {
mCb.onTaskViewClicked(tv, tv.getTask(), (v == mFooterView));
}
}
}, 125);
}
/**** View.OnLongClickListener Implementation ****/
@Override
public boolean onLongClick(View v) {
if (v == mBarView.mApplicationIcon) {

View File

@ -16,7 +16,12 @@
package com.android.systemui.recents.views;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.animation.Interpolator;
import com.android.systemui.recents.Constants;
/* The transform state for a task view */
@ -77,6 +82,73 @@ public class TaskViewTransform {
return (Float.compare(translationZ, v) != 0);
}
/** Applies this transform to a view. */
public void applyToTaskView(View v, int duration, Interpolator interp,
ValueAnimator.AnimatorUpdateListener scaleUpdateListener) {
// Check to see if any properties have changed, and update the task view
if (duration > 0) {
ViewPropertyAnimator anim = v.animate();
boolean useLayers = false;
// Animate to the final state
if (hasTranslationYChangedFrom(v.getTranslationY())) {
anim.translationY(translationY);
}
if (Constants.DebugFlags.App.EnableShadows &&
hasTranslationZChangedFrom(v.getTranslationZ())) {
anim.translationZ(translationZ);
}
if (hasScaleChangedFrom(v.getScaleX())) {
anim.scaleX(scale)
.scaleY(scale)
.setUpdateListener(scaleUpdateListener);
useLayers = true;
}
if (hasAlphaChangedFrom(v.getAlpha())) {
// Use layers if we animate alpha
anim.alpha(alpha);
useLayers = true;
}
if (useLayers) {
anim.withLayer();
}
anim.setStartDelay(startDelay)
.setDuration(duration)
.setInterpolator(interp)
.start();
} else {
// Set the changed properties
if (hasTranslationYChangedFrom(v.getTranslationY())) {
v.setTranslationY(translationY);
}
if (Constants.DebugFlags.App.EnableShadows &&
hasTranslationZChangedFrom(v.getTranslationZ())) {
v.setTranslationZ(translationZ);
}
if (hasScaleChangedFrom(v.getScaleX())) {
v.setScaleX(scale);
v.setScaleY(scale);
scaleUpdateListener.onAnimationUpdate(null);
}
if (hasAlphaChangedFrom(v.getAlpha())) {
v.setAlpha(alpha);
}
}
}
/** Reset the transform on a view. */
public static void reset(View v) {
v.setTranslationX(0f);
v.setTranslationY(0f);
if (Constants.DebugFlags.App.EnableShadows) {
v.setTranslationZ(0f);
}
v.setScaleX(1f);
v.setScaleY(1f);
v.setAlpha(1f);
v.invalidate();
}
@Override
public String toString() {
return "TaskViewTransform delay: " + startDelay + " y: " + translationY + " z: " + translationZ +

View File

@ -16,6 +16,7 @@
package com.android.systemui.recents.views;
import android.graphics.Rect;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
/* Common code related to view animations */
@ -23,27 +24,22 @@ public class ViewAnimation {
/* The animation context for a task view animation into Recents */
public static class TaskViewEnterContext {
// The full screenshot view that we are animating down
FullscreenTransitionOverlayView fullScreenshotView;
// The transform of the target task view that we are animating into
TaskViewTransform targetTaskTransform;
// A trigger to run some logic when all the animations complete. This works around the fact
// that it is difficult to coordinate ViewPropertyAnimators
ReferenceCountedTrigger postAnimationTrigger;
// These following properties are updated for each task view we start the enter animation on
// The task rect for the current stack
Rect currentTaskRect;
// The transform of the current task view
TaskViewTransform currentTaskTransform;
// Whether this is the front most task view
boolean isCurrentTaskLaunchTarget;
// The view index of the current task view
int currentStackViewIndex;
// The total number of task views
int currentStackViewCount;
public TaskViewEnterContext(FullscreenTransitionOverlayView fss, ReferenceCountedTrigger t) {
fullScreenshotView = fss;
public TaskViewEnterContext(ReferenceCountedTrigger t) {
postAnimationTrigger = t;
}
}
@ -53,6 +49,7 @@ public class ViewAnimation {
// A trigger to run some logic when all the animations complete. This works around the fact
// that it is difficult to coordinate ViewPropertyAnimators
ReferenceCountedTrigger postAnimationTrigger;
// The translationY to apply to a TaskView to move it off the bottom of the task stack
int offscreenTranslationY;