diff options
Diffstat (limited to 'packages/SystemUI/src/com/android/systemui/recents')
18 files changed, 925 insertions, 891 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java index 66e5d14..591149c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java @@ -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); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 5741f22..7d69b94 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index a0cab5c..55711cf0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -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); } } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index 3b436f0..fb77751 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -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; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index 4cf9235..41874fc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java new file mode 100644 index 0000000..9076818 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java @@ -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; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DebugOverlayView.java b/packages/SystemUI/src/com/android/systemui/recents/views/DebugOverlayView.java new file mode 100644 index 0000000..6c90fe3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/DebugOverlayView.java @@ -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); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java index 3adee0e..4b5c0bd 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java @@ -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; @@ -49,13 +48,6 @@ public class FixedSizeImageView extends ImageView { } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - mFixedWidth = getMeasuredWidth(); - mFixedHeight = getMeasuredHeight(); - } - - @Override public void requestLayout() { if (mAllowRelayout) { super.requestLayout(); @@ -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; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java deleted file mode 100644 index 63f59be..0000000 --- a/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java +++ /dev/null @@ -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; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index b6479db..356841f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java index deb9df3..fbfad5c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java @@ -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); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskFooterView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskFooterView.java new file mode 100644 index 0000000..95af1c9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskFooterView.java @@ -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); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 8b86258..d84a40f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -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; - } - } - } - - // 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); - } + /** 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); + } - // 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); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java index 65407a6..3c89cd7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java index 08a25f1..1116d51 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java @@ -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); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index 4757c5f..d1b33f3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -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,38 +93,36 @@ 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; + mMaxDim = mConfig.taskStackMaxDim; + mClipViewInStack = true; + mViewBounds = new AnimateableViewBounds(this, mConfig.taskViewRoundedCornerRadiusPx); 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); - } - }); + setOutlineProvider(mViewBounds); } - @Override - protected void onFinishInflate() { - mMaxDim = mConfig.taskStackMaxDim; + /** Set callback */ + void setCallbacks(TaskViewCallbacks cb) { + mCb = cb; + } - // By default, all views are clipped to other views in their stack - mClipViewInStack = true; + /** Gets the task */ + Task getTask() { + 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); - mLockToAppButtonView = findViewById(R.id.lock_to_app); - - if (mTaskDataLoaded) { - onTaskDataLoaded(); - } + mFooterView = (TaskFooterView) findViewById(R.id.lock_to_app); + mFooterView.setCallbacks(this); } @Override @@ -148,96 +130,48 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); - // Measure the bar view, thumbnail, and lock-to-app buttons + // Measure the bar view, thumbnail, and footer mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY)); - mLockToAppButtonView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + mFooterView.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)); + 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); } - /** Set callback */ - void setCallbacks(TaskViewCallbacks cb) { - mCb = cb; - } - - /** Gets the task */ - Task getTask() { - return mTask; - } - /** 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) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java index b351b03..d583c20 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java @@ -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 + diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java index e50a5cf..3705cb5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java @@ -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; |