diff options
33 files changed, 768 insertions, 316 deletions
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index b86621f235f8..443045e55e89 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -796,6 +796,12 @@ public class ActivityManager { public static final int RECENT_INCLUDE_PROFILES = 0x0004; /** + * Ignores all tasks that are on the home stack. + * @hide + */ + public static final int RECENT_IGNORE_HOME_STACK_TASKS = 0x0008; + + /** * <p></p>Return a list of the tasks that the user has recently launched, with * the most recent being first and older ones after in order. * diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 4c02314bec1f..b13ef3a9d00a 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -128,6 +128,10 @@ public class ActivityOptions { public static final int ANIM_DEFAULT = 6; /** @hide */ public static final int ANIM_LAUNCH_TASK_BEHIND = 7; + /** @hide */ + public static final int ANIM_THUMBNAIL_ASPECT_SCALE_UP = 8; + /** @hide */ + public static final int ANIM_THUMBNAIL_ASPECT_SCALE_DOWN = 9; private String mPackageName; private int mAnimationType = ANIM_NONE; @@ -338,6 +342,67 @@ public class ActivityOptions { } /** + * Create an ActivityOptions specifying an animation where the new activity + * window and a thumbnail is aspect-scaled to a new location. + * + * @param source The View that this thumbnail is animating from. This + * defines the coordinate space for <var>startX</var> and <var>startY</var>. + * @param thumbnail The bitmap that will be shown as the initial thumbnail + * of the animation. + * @param startX The x starting location of the bitmap, relative to <var>source</var>. + * @param startY The y starting location of the bitmap, relative to <var>source</var>. + * @param listener Optional OnAnimationStartedListener to find out when the + * requested animation has started running. If for some reason the animation + * is not executed, the callback will happen immediately. + * @return Returns a new ActivityOptions object that you can use to + * supply these options as the options Bundle when starting an activity. + * @hide + */ + public static ActivityOptions makeThumbnailAspectScaleUpAnimation(View source, + Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) { + return makeAspectScaledThumbnailAnimation(source, thumbnail, startX, startY, listener, + true); + } + + /** + * Create an ActivityOptions specifying an animation where the new activity + * window and a thumbnail is aspect-scaled to a new location. + * + * @param source The View that this thumbnail is animating to. This + * defines the coordinate space for <var>startX</var> and <var>startY</var>. + * @param thumbnail The bitmap that will be shown as the final thumbnail + * of the animation. + * @param startX The x end location of the bitmap, relative to <var>source</var>. + * @param startY The y end location of the bitmap, relative to <var>source</var>. + * @param listener Optional OnAnimationStartedListener to find out when the + * requested animation has started running. If for some reason the animation + * is not executed, the callback will happen immediately. + * @return Returns a new ActivityOptions object that you can use to + * supply these options as the options Bundle when starting an activity. + * @hide + */ + public static ActivityOptions makeThumbnailAspectScaleDownAnimation(View source, + Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) { + return makeAspectScaledThumbnailAnimation(source, thumbnail, startX, startY, listener, + false); + } + + private static ActivityOptions makeAspectScaledThumbnailAnimation(View source, Bitmap thumbnail, + int startX, int startY, OnAnimationStartedListener listener, boolean scaleUp) { + ActivityOptions opts = new ActivityOptions(); + opts.mPackageName = source.getContext().getPackageName(); + opts.mAnimationType = scaleUp ? ANIM_THUMBNAIL_ASPECT_SCALE_UP : + ANIM_THUMBNAIL_ASPECT_SCALE_DOWN; + opts.mThumbnail = thumbnail; + int[] pts = new int[2]; + source.getLocationOnScreen(pts); + opts.mStartX = pts[0] + startX; + opts.mStartY = pts[1] + startY; + opts.setOnAnimationStartedListener(source.getHandler(), listener); + return opts; + } + + /** * Create an ActivityOptions to transition between Activities using cross-Activity scene * animations. This method carries the position of one shared element to the started Activity. * The position of <code>sharedElement</code> will be used as the epicenter for the @@ -479,7 +544,9 @@ public class ActivityOptions { case ANIM_THUMBNAIL_SCALE_UP: case ANIM_THUMBNAIL_SCALE_DOWN: - mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL); + case ANIM_THUMBNAIL_ASPECT_SCALE_UP: + case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN: + mThumbnail = (Bitmap) opts.getParcelable(KEY_ANIM_THUMBNAIL); mStartX = opts.getInt(KEY_ANIM_START_X, 0); mStartY = opts.getInt(KEY_ANIM_START_Y, 0); mAnimationStartedListener = IRemoteCallback.Stub.asInterface( @@ -630,6 +697,8 @@ public class ActivityOptions { break; case ANIM_THUMBNAIL_SCALE_UP: case ANIM_THUMBNAIL_SCALE_DOWN: + case ANIM_THUMBNAIL_ASPECT_SCALE_UP: + case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN: mThumbnail = otherOptions.mThumbnail; mStartX = otherOptions.mStartX; mStartY = otherOptions.mStartY; @@ -686,6 +755,8 @@ public class ActivityOptions { break; case ANIM_THUMBNAIL_SCALE_UP: case ANIM_THUMBNAIL_SCALE_DOWN: + case ANIM_THUMBNAIL_ASPECT_SCALE_UP: + case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN: b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail); b.putInt(KEY_ANIM_START_X, mStartX); b.putInt(KEY_ANIM_START_Y, mStartY); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 7deb9c89f79f..5d6d99820b44 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -93,6 +93,8 @@ interface IWindowManager int startHeight); void overridePendingAppTransitionThumb(in Bitmap srcThumb, int startX, int startY, IRemoteCallback startedCallback, boolean scaleUp); + void overridePendingAppTransitionAspectScaledThumb(in Bitmap srcThumb, int startX, + int startY, IRemoteCallback startedCallback, boolean scaleUp); void executeAppTransition(); void setAppStartingWindow(IBinder token, String pkg, int theme, in CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml index 3180e585ba38..c0d999546399 100644 --- a/core/res/res/values-sw600dp/dimens.xml +++ b/core/res/res/values-sw600dp/dimens.xml @@ -19,9 +19,9 @@ --> <resources> <!-- The width that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_width">512dp</dimen> + <dimen name="thumbnail_width">360dp</dimen> <!-- The height that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_height">512dp</dimen> + <dimen name="thumbnail_height">360dp</dimen> <!-- The maximum number of action buttons that should be permitted within an action bar/action mode. This will be used to determine how many showAsAction="ifRoom" items can fit. "always" items can override this. --> diff --git a/packages/SystemUI/res/anim/recents_from_launcher_enter.xml b/packages/SystemUI/res/anim/recents_from_launcher_enter.xml index 305a82f7c93c..4e1d66dedda6 100644 --- a/packages/SystemUI/res/anim/recents_from_launcher_enter.xml +++ b/packages/SystemUI/res/anim/recents_from_launcher_enter.xml @@ -20,9 +20,9 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false" android:zAdjustment="normal"> - <alpha android:fromAlpha="1.0" android:toAlpha="1.0" + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" android:interpolator="@android:interpolator/linear" - android:duration="200"/> + android:duration="100"/> </set> diff --git a/packages/SystemUI/res/anim/recents_from_launcher_exit.xml b/packages/SystemUI/res/anim/recents_from_launcher_exit.xml index 863591f9d221..afab78d0a1ec 100644 --- a/packages/SystemUI/res/anim/recents_from_launcher_exit.xml +++ b/packages/SystemUI/res/anim/recents_from_launcher_exit.xml @@ -24,5 +24,5 @@ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" android:interpolator="@android:interpolator/linear_out_slow_in" - android:duration="200"/> + android:duration="100"/> </set> diff --git a/packages/SystemUI/res/anim/recents_to_launcher_enter.xml b/packages/SystemUI/res/anim/recents_to_launcher_enter.xml index adcefe074b02..4e1d66dedda6 100644 --- a/packages/SystemUI/res/anim/recents_to_launcher_enter.xml +++ b/packages/SystemUI/res/anim/recents_to_launcher_enter.xml @@ -23,6 +23,6 @@ <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" - android:interpolator="@android:interpolator/fast_out_linear_in" - android:duration="200"/> + android:interpolator="@android:interpolator/linear" + android:duration="100"/> </set> diff --git a/packages/SystemUI/res/anim/recents_to_launcher_exit.xml b/packages/SystemUI/res/anim/recents_to_launcher_exit.xml index 863591f9d221..afab78d0a1ec 100644 --- a/packages/SystemUI/res/anim/recents_to_launcher_exit.xml +++ b/packages/SystemUI/res/anim/recents_to_launcher_exit.xml @@ -24,5 +24,5 @@ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" android:interpolator="@android:interpolator/linear_out_slow_in" - android:duration="200"/> + android:duration="100"/> </set> diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml index 5253ee07e7e8..4cb8498e07d0 100644 --- a/packages/SystemUI/res/layout/recents_task_view.xml +++ b/packages/SystemUI/res/layout/recents_task_view.xml @@ -23,45 +23,7 @@ android:id="@+id/task_view_thumbnail" android:layout_width="match_parent" android:layout_height="match_parent" /> - <com.android.systemui.recents.views.TaskViewHeader - android:id="@+id/task_view_bar" - android:layout_width="match_parent" - android:layout_height="@dimen/recents_task_bar_height" - android:layout_gravity="top|center_horizontal"> - <com.android.systemui.recents.views.FixedSizeImageView - android:id="@+id/application_icon" - android:layout_width="@dimen/recents_task_view_application_icon_size" - android:layout_height="@dimen/recents_task_view_application_icon_size" - android:layout_marginStart="8dp" - android:layout_gravity="center_vertical|start" - android:padding="8dp" - android:background="@drawable/recents_button_bg" /> - <TextView - android:id="@+id/activity_description" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical|start" - android:layout_marginStart="64dp" - android:layout_marginEnd="64dp" - android:textSize="16sp" - android:textColor="#ffffffff" - android:text="@string/recents_empty_message" - android:fontFamily="sans-serif-medium" - android:singleLine="true" - android:maxLines="2" - android:ellipsize="marquee" - android:fadingEdge="horizontal" /> - <com.android.systemui.recents.views.FixedSizeImageView - android:id="@+id/dismiss_task" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginEnd="4dp" - android:layout_gravity="center_vertical|end" - android:padding="12dp" - android:background="@drawable/recents_button_bg" - android:visibility="invisible" - android:src="@drawable/recents_dismiss_light" /> - </com.android.systemui.recents.views.TaskViewHeader> + <include layout="@layout/recents_task_view_header" /> <FrameLayout android:id="@+id/lock_to_app_fab" android:layout_width="48dp" diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml new file mode 100644 index 000000000000..f1d8ad0e4b8e --- /dev/null +++ b/packages/SystemUI/res/layout/recents_task_view_header.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<com.android.systemui.recents.views.TaskViewHeader + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/task_view_bar" + android:layout_width="match_parent" + android:layout_height="@dimen/recents_task_bar_height" + android:layout_gravity="top|center_horizontal"> + <com.android.systemui.recents.views.FixedSizeImageView + android:id="@+id/application_icon" + android:layout_width="@dimen/recents_task_view_application_icon_size" + android:layout_height="@dimen/recents_task_view_application_icon_size" + android:layout_marginStart="8dp" + android:layout_gravity="center_vertical|start" + android:padding="8dp" + android:background="@drawable/recents_button_bg" /> + <TextView + android:id="@+id/activity_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|start" + android:layout_marginStart="64dp" + android:layout_marginEnd="64dp" + android:textSize="16sp" + android:textColor="#ffffffff" + android:text="@string/recents_empty_message" + android:fontFamily="sans-serif-medium" + android:singleLine="true" + android:maxLines="2" + android:ellipsize="marquee" + android:fadingEdge="horizontal" /> + <com.android.systemui.recents.views.FixedSizeImageView + android:id="@+id/dismiss_task" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginEnd="4dp" + android:layout_gravity="center_vertical|end" + android:padding="12dp" + android:background="@drawable/recents_button_bg" + android:visibility="invisible" + android:src="@drawable/recents_dismiss_light" /> +</com.android.systemui.recents.views.TaskViewHeader>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 52dc000198ec..fa00ebf16a2b 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -126,7 +126,7 @@ <integer name="recents_animate_task_bar_enter_duration">275</integer> <!-- The animation delay for animating the first task in. This should roughly be the animation duration of the transition in to recents. --> - <integer name="recents_animate_task_bar_enter_delay">225</integer> + <integer name="recents_animate_task_bar_enter_delay">300</integer> <!-- The min animation duration for animating the task bar out. --> <integer name="recents_animate_task_exit_to_home_duration">225</integer> <!-- The min animation duration for animating the task bar out. --> diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java index 354eb55a1880..8710aa25109c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java @@ -22,6 +22,7 @@ import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; @@ -29,6 +30,7 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.os.Handler; import android.os.UserHandle; +import android.view.LayoutInflater; import android.view.View; import com.android.systemui.R; import com.android.systemui.RecentsComponent; @@ -41,6 +43,7 @@ import com.android.systemui.recents.model.TaskGrouping; import com.android.systemui.recents.model.TaskStack; import com.android.systemui.recents.views.TaskStackView; import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm; +import com.android.systemui.recents.views.TaskViewHeader; import com.android.systemui.recents.views.TaskViewTransform; import java.util.ArrayList; @@ -86,6 +89,10 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta int mNavBarHeight; int mNavBarWidth; + // Header (for transition) + TaskViewHeader mHeaderBar; + TaskStackView mDummyStackView; + // Variables to keep track of if we need to start recents after binding View mStatusBarView; boolean mTriggeredFromAltTab; @@ -114,6 +121,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta public void onStart() { // Initialize some static datastructures TaskStackViewLayoutAlgorithm.initializeCurve(); + reloadHeaderBarLayout(); } public void onBootCompleted() { @@ -169,7 +177,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta void showRelativeAffiliatedTask(boolean showNextTask) { TaskStack stack = RecentsTaskLoader.getShallowTaskStack(mSystemServicesProxy, - Integer.MAX_VALUE); + Integer.MAX_VALUE, mContext.getResources()); // Return early if there are no tasks if (stack.getTaskCount() == 0) return; @@ -250,6 +258,28 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta mSystemInsets.set(0, mStatusBarHeight, 0, mNavBarHeight); } sLastScreenshot = null; + reloadHeaderBarLayout(); + } + + /** Prepares the header bar layout. */ + void reloadHeaderBarLayout() { + // Inflate the header bar layout so that we can rebind and draw it for the transition + Resources res = mContext.getResources(); + TaskStack stack = new TaskStack(); + mDummyStackView = new TaskStackView(mContext, stack); + TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm(); + Rect taskStackBounds = new Rect(mTaskStackBounds); + taskStackBounds.bottom -= mSystemInsets.bottom; + algo.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds); + Rect taskViewSize = algo.getUntransformedTaskViewSize(); + int taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height); + LayoutInflater inflater = LayoutInflater.from(mContext); + mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header, null, + false); + mHeaderBar.measure( + View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(taskBarHeight, View.MeasureSpec.EXACTLY)); + mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight); } /** Gets the top task. */ @@ -361,27 +391,37 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } } - // If the screenshot fails, then load the first task thumbnail and use that - Bitmap firstThumbnail = mSystemServicesProxy.getTaskThumbnail(topTask.id); - if (firstThumbnail != null) { - // Update the destination rect - Rect toTaskRect = getThumbnailTransitionRect(topTask.id, isTopTaskHome); - if (toTaskRect.width() > 0 && toTaskRect.height() > 0) { - // Create the new thumbnail for the animation down - // XXX: We should find a way to optimize this so we don't need to create a new bitmap - Bitmap thumbnail = Bitmap.createBitmap(toTaskRect.width(), toTaskRect.height(), - Bitmap.Config.ARGB_8888); - int size = Math.min(firstThumbnail.getWidth(), firstThumbnail.getHeight()); + // Update the destination rect + Task toTask = new Task(); + TaskViewTransform toTransform = getThumbnailTransitionTransform(topTask.id, isTopTaskHome, + toTask); + if (toTransform != null && toTask.key != null) { + Rect toTaskRect = toTransform.rect; + ActivityInfo info = mSystemServicesProxy.getActivityInfo( + toTask.key.baseIntent.getComponent(), toTask.key.userId); + if (toTask.activityIcon == null) { + toTask.activityIcon = mSystemServicesProxy.getActivityIcon(info, + toTask.key.userId); + } + if (toTask.activityLabel == null) { + toTask.activityLabel = mSystemServicesProxy.getActivityLabel(info); + } + + Bitmap thumbnail = Bitmap.createBitmap(toTaskRect.width(), toTaskRect.height(), + Bitmap.Config.ARGB_8888); + if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) { + thumbnail.eraseColor(0xFFff0000); + } else { Canvas c = new Canvas(thumbnail); - c.drawBitmap(firstThumbnail, new Rect(0, 0, size, size), - new Rect(0, 0, toTaskRect.width(), toTaskRect.height()), null); + c.scale(toTransform.scale, toTransform.scale); + mHeaderBar.rebindToTask(toTask); + mHeaderBar.draw(c); c.setBitmap(null); - // Recycle the old thumbnail - firstThumbnail.recycle(); - mStartAnimationTriggered = false; - return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView, - thumbnail, toTaskRect.left, toTaskRect.top, this); } + + mStartAnimationTriggered = false; + return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mStatusBarView, + thumbnail, toTaskRect.left, toTaskRect.top, this); } // If both the screenshot and thumbnail fails, then just fall back to the default transition @@ -389,21 +429,18 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } /** Returns the transition rect for the given task id. */ - Rect getThumbnailTransitionRect(int runningTaskId, boolean isTopTaskHome) { + TaskViewTransform getThumbnailTransitionTransform(int runningTaskId, boolean isTopTaskHome, + Task runningTaskOut) { // Get the stack of tasks that we are animating into - TaskStack stack = RecentsTaskLoader.getShallowTaskStack(mSystemServicesProxy, -1); + TaskStack stack = RecentsTaskLoader.getShallowTaskStack(mSystemServicesProxy, -1, + mContext.getResources()); if (stack.getTaskCount() == 0) { - return new Rect(); + return null; } // Get the stack - TaskStackView tsv = new TaskStackView(mContext, stack); - TaskStackViewLayoutAlgorithm algo = tsv.getStackAlgorithm(); - Rect taskStackBounds = new Rect(mTaskStackBounds); - taskStackBounds.bottom -= mSystemInsets.bottom; - tsv.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds, - mTriggeredFromAltTab, isTopTaskHome); - tsv.getScroller().setStackScrollToInitialState(); + mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome); + mDummyStackView.getScroller().setStackScrollToInitialState(); // Find the running task in the TaskStack Task task = null; @@ -415,6 +452,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta Task t = tasks.get(i); if (t.key.id == runningTaskId) { task = t; + runningTaskOut.copyFrom(t); break; } } @@ -425,8 +463,9 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } // Get the transform for the running task - mTmpTransform = algo.getStackTransform(task, tsv.getScroller().getStackScroll(), mTmpTransform, null); - return new Rect(mTmpTransform.rect); + mTmpTransform = mDummyStackView.getStackAlgorithm().getStackTransform(task, + mDummyStackView.getScroller().getStackScroll(), mTmpTransform, null); + return mTmpTransform; } /** Starts the recents activity */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index 3709c4364184..6f4cf6b1cb27 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -27,6 +27,8 @@ public class Constants { public static class App { // Enables the screenshot app->Recents transition public static final boolean EnableScreenshotAppTransition = false; + // Enables debug drawing for the transition thumbnail + public static final boolean EnableTransitionThumbnailDebugMode = false; // Enables the filtering of tasks according to their grouping public static final boolean EnableTaskFiltering = false; // Enables clipping of tasks against each other diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 7fafe7aa28d1..a5b845d00e7a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -384,7 +384,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // Private API calls to make the shadows look better try { - Utilities.setShadowProperty("ambientShadowStrength", String.valueOf(35f)); Utilities.setShadowProperty("ambientRatio", String.valueOf(1.5f)); } catch (IllegalAccessException e) { e.printStackTrace(); 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 5390daf6db00..b29f3780eb4a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -174,6 +174,7 @@ public class SystemServicesProxy { int minNumTasksToQuery = 10; int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks); List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery, + ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS | ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_PROFILES | ActivityManager.RECENT_WITH_EXCLUDED, userId); @@ -185,11 +186,6 @@ public class SystemServicesProxy { // NOTE: The order of these checks happens in the expected order of the traversal of the // tasks - // Skip tasks from this Recents package - if (t.baseIntent.getComponent().getPackageName().equals(mRecentsPackage)) { - iter.remove(); - continue; - } // Check the first non-recents task, include this task even if it is marked as excluded // from recents. In other words, only remove excluded tasks if it is not the first task boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) @@ -199,11 +195,6 @@ public class SystemServicesProxy { continue; } isFirstValidTask = false; - // Skip tasks in the home stack - if (isInHomeStack(t.persistentId)) { - iter.remove(); - continue; - } } return tasks.subList(0, Math.min(tasks.size(), numLatestTasks)); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java index 0e2f37038283..b93c1260fac2 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java @@ -18,6 +18,7 @@ package com.android.systemui.recents.model; import android.app.ActivityManager; import android.content.ComponentCallbacks2; +import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Resources; @@ -31,6 +32,7 @@ import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.SystemServicesProxy; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; @@ -171,9 +173,9 @@ class TaskResourceLoader implements Runnable { // Load the application icon if it is stale or we haven't cached one yet if (cachedIcon == null) { ActivityInfo info = ssp.getActivityInfo(t.key.baseIntent.getComponent(), - t.userId); + t.key.userId); if (info != null) { - cachedIcon = ssp.getActivityIcon(info, t.userId); + cachedIcon = ssp.getActivityIcon(info, t.key.userId); } if (cachedIcon == null) { cachedIcon = mDefaultApplicationIcon; @@ -228,8 +230,10 @@ public class RecentsTaskLoader { static RecentsTaskLoader sInstance; SystemServicesProxy mSystemServicesProxy; + DrawableLruCache mTaskDescriptionIconCache; DrawableLruCache mApplicationIconCache; BitmapLruCache mThumbnailCache; + StringLruCache mActivityLabelCache; TaskResourceLoadQueue mLoadQueue; TaskResourceLoader mLoader; @@ -270,8 +274,10 @@ public class RecentsTaskLoader { mSystemServicesProxy = new SystemServicesProxy(context); mPackageMonitor = new RecentsPackageMonitor(); mLoadQueue = new TaskResourceLoadQueue(); + mTaskDescriptionIconCache = new DrawableLruCache(iconCacheSize); mApplicationIconCache = new DrawableLruCache(iconCacheSize); mThumbnailCache = new BitmapLruCache(thumbnailCacheSize); + mActivityLabelCache = new StringLruCache(100); mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache, mDefaultThumbnail, mDefaultApplicationIcon); } @@ -313,6 +319,7 @@ public class RecentsTaskLoader { RecentsConfiguration config = RecentsConfiguration.getInstance(); Resources res = context.getResources(); LinkedHashSet<Task> tasksToLoad = new LinkedHashSet<Task>(); + ArrayList<Task> tasksToAdd = new ArrayList<Task>(); TaskStack stack = new TaskStack(); SpaceNode root = new SpaceNode(); root.setStack(stack); @@ -325,55 +332,80 @@ public class RecentsTaskLoader { int taskCount = tasks.size(); for (int i = 0; i < taskCount; i++) { ActivityManager.RecentTaskInfo t = tasks.get(i); - ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId); - if (info == null) continue; + Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId, + t.firstActiveTime, t.lastActiveTime); + ComponentName cn = t.baseIntent.getComponent(); + ActivityInfo info = null; ActivityManager.TaskDescription av = t.taskDescription; - String activityLabel = null; + String activityLabel = null; Drawable activityIcon = mDefaultApplicationIcon; int activityColor = config.taskBarViewDefaultBackgroundColor; + boolean loadedActivityIcon = false; if (av != null) { - activityLabel = (av.getLabel() != null ? av.getLabel() : ssp.getActivityLabel(info)); - activityIcon = (av.getIcon() != null) ? + activityLabel = av.getLabel(); + activityIcon = mTaskDescriptionIconCache.getAndInvalidateIfModified(taskKey); + if (activityIcon == null) { + activityIcon = (av.getIcon() != null) ? ssp.getBadgedIcon(new BitmapDrawable(res, av.getIcon()), t.userId) : null; + if (activityIcon != null) { + mTaskDescriptionIconCache.put(taskKey, activityIcon); + } + } if (av.getPrimaryColor() != 0) { activityColor = av.getPrimaryColor(); } - } else { - activityLabel = ssp.getActivityLabel(info); + loadedActivityIcon = (activityIcon != null); + } + // If there is no activity label, then try and read it from the label cache before + // loading it from the system + if (activityLabel == null) { + activityLabel = mActivityLabelCache.getAndInvalidateIfModified(taskKey); + if (activityLabel == null) { + if (info == null) { + info = ssp.getActivityInfo(cn, t.userId); + } + activityLabel = ssp.getActivityLabel(info); + mActivityLabelCache.put(taskKey, activityLabel); + } } // Create a new task - Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, t.affiliatedTaskId, - t.affiliatedTaskColor, activityLabel, activityIcon, activityColor, t.userId, - t.firstActiveTime, t.lastActiveTime, (i == (taskCount - 1)), + Task task = new Task(taskKey, (t.id > -1), t.affiliatedTaskId, t.affiliatedTaskColor, + activityLabel, activityIcon, activityColor, (i == (taskCount - 1)), config.lockToAppEnabled); // Preload the specified number of apps if (i >= (taskCount - preloadCount)) { - // Load the icon from the cache if possible - task.applicationIcon = mApplicationIconCache.getAndInvalidateIfModified(task.key); - if (task.applicationIcon == null) { - // Load the icon from the system - task.applicationIcon = ssp.getActivityIcon(info, task.userId); - if (task.applicationIcon != null) { - mApplicationIconCache.put(task.key, task.applicationIcon); + // Load the icon from the cache if possible (only if we don't have an activity icon) + if (!loadedActivityIcon) { + task.applicationIcon = + mApplicationIconCache.getAndInvalidateIfModified(taskKey); + if (task.applicationIcon == null) { + // Load the icon from the system + if (info == null) { + info = ssp.getActivityInfo(cn, t.userId); + } + task.applicationIcon = ssp.getActivityIcon(info, taskKey.userId); + if (task.applicationIcon != null) { + mApplicationIconCache.put(taskKey, task.applicationIcon); + } + } + if (task.applicationIcon == null) { + // Either the task has changed since the last active time, or it was not + // previously cached, so try and load the task anew. + tasksToLoad.add(task); } - } - if (task.applicationIcon == null) { - // Either the task has changed since the last active time, or it was not - // previously cached, so try and load the task anew. - tasksToLoad.add(task); } // Load the thumbnail from the cache if possible - task.thumbnail = mThumbnailCache.getAndInvalidateIfModified(task.key); + task.thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey); if (task.thumbnail == null) { // Load the thumbnail from the system - task.thumbnail = ssp.getTaskThumbnail(task.key.id); + task.thumbnail = ssp.getTaskThumbnail(taskKey.id); if (task.thumbnail != null) { task.thumbnail.setHasAlpha(false); - mThumbnailCache.put(task.key, task.thumbnail); + mThumbnailCache.put(taskKey, task.thumbnail); } } if (task.thumbnail == null) { @@ -384,10 +416,11 @@ public class RecentsTaskLoader { } // Add the task to the stack - stack.addTask(task); + tasksToAdd.add(task); } // Simulate the groupings that we describe + stack.setTasks(tasksToAdd); stack.createAffiliatedGroupings(config); // Start the task loader and add all the tasks we need to load @@ -401,21 +434,29 @@ public class RecentsTaskLoader { } /** Creates a lightweight stack of the current recent tasks, without thumbnails and icons. */ - public static TaskStack getShallowTaskStack(SystemServicesProxy ssp, int numTasks) { + public static TaskStack getShallowTaskStack(SystemServicesProxy ssp, int numTasks, + Resources resources) { RecentsConfiguration config = RecentsConfiguration.getInstance(); List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp, numTasks); + ArrayList<Task> tasksToAdd = new ArrayList<Task>(); TaskStack stack = new TaskStack(); int taskCount = tasks.size(); for (int i = 0; i < taskCount; i++) { ActivityManager.RecentTaskInfo t = tasks.get(i); - ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId); - if (info == null) continue; + ActivityManager.TaskDescription av = t.taskDescription; - stack.addTask(new Task(t.persistentId, true, t.baseIntent, t.affiliatedTaskId, - t.affiliatedTaskColor, null, null, 0, 0, t.firstActiveTime, t.lastActiveTime, - (i == (taskCount - 1)), config.lockToAppEnabled)); + BitmapDrawable icon = null; + if (av.getIcon() != null) { + icon = new BitmapDrawable(resources, av.getIcon()); + } + Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId, + t.firstActiveTime, t.lastActiveTime); + tasksToAdd.add(new Task(taskKey, true, t.affiliatedTaskId, t.affiliatedTaskColor, + av.getLabel(), icon, av.getPrimaryColor(), (i == (taskCount - 1)), + config.lockToAppEnabled)); } + stack.setTasks(tasksToAdd); stack.createAffiliatedGroupings(config); return stack; } @@ -484,18 +525,23 @@ public class RecentsTaskLoader { // We are leaving recents, so trim the data a bit mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2); mApplicationIconCache.trimToSize(mMaxIconCacheSize / 2); + mTaskDescriptionIconCache.trimToSize(mMaxIconCacheSize / 2); break; case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: case ComponentCallbacks2.TRIM_MEMORY_MODERATE: // We are going to be low on memory mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4); mApplicationIconCache.trimToSize(mMaxIconCacheSize / 4); + mTaskDescriptionIconCache.trimToSize(mMaxIconCacheSize / 4); break; case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: // We are low on memory, so release everything mThumbnailCache.evictAll(); mApplicationIconCache.evictAll(); + mTaskDescriptionIconCache.evictAll(); + // The cache is small, only clear the label cache when we are critical + mActivityLabelCache.evictAll(); break; default: break; diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java new file mode 100644 index 000000000000..b06c4548c0ae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java @@ -0,0 +1,32 @@ +/* + * 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.model; + +/** + * The String LRU cache. + */ +class StringLruCache extends KeyStoreLruCache<String> { + public StringLruCache(int cacheSize) { + super(cacheSize); + } + + @Override + protected int computeSize(String s) { + // The cache size is measured in number of strings + return 1; + } +}
\ No newline at end of file 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 f6c3a7ed8ed1..977db602f9b8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -88,7 +88,6 @@ public class Task { public boolean isActive; public boolean lockToThisTask; public boolean lockToTaskEnabled; - public int userId; TaskCallbacks mCb; @@ -96,13 +95,12 @@ public class Task { // Only used by RecentsService for task rect calculations. } - public Task(int id, boolean isActive, Intent intent, int taskAffiliation, int taskAffiliationColor, - String activityTitle, Drawable activityIcon, int colorPrimary, int userId, - long firstActiveTime, long lastActiveTime, boolean lockToThisTask, - boolean lockToTaskEnabled) { - boolean isInAffiliationGroup = (taskAffiliation != id); + public Task(TaskKey key, boolean isActive, int taskAffiliation, int taskAffiliationColor, + String activityTitle, Drawable activityIcon, int colorPrimary, + boolean lockToThisTask, boolean lockToTaskEnabled) { + boolean isInAffiliationGroup = (taskAffiliation != key.id); boolean hasAffiliationGroupColor = isInAffiliationGroup && (taskAffiliationColor != 0); - this.key = new TaskKey(id, intent, userId, firstActiveTime, lastActiveTime); + this.key = key; this.taskAffiliation = taskAffiliation; this.taskAffiliationColor = taskAffiliationColor; this.activityLabel = activityTitle; @@ -113,7 +111,20 @@ public class Task { this.isActive = isActive; this.lockToThisTask = lockToTaskEnabled && lockToThisTask; this.lockToTaskEnabled = lockToTaskEnabled; - this.userId = userId; + } + + /** Copies the other task. */ + public void copyFrom(Task o) { + this.key = o.key; + this.taskAffiliation = o.taskAffiliation; + this.taskAffiliationColor = o.taskAffiliationColor; + this.activityLabel = o.activityLabel; + this.activityIcon = o.activityIcon; + this.colorPrimary = o.colorPrimary; + this.useLightOnPrimaryColor = o.useLightOnPrimaryColor; + this.isActive = o.isActive; + this.lockToThisTask = o.lockToThisTask; + this.lockToTaskEnabled = o.lockToTaskEnabled; } /** Set the callbacks */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index 98bf151b3fab..1e47b504baf4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -92,7 +92,10 @@ class FilteredTaskList { /** Returns the index of this task in the list of filtered tasks */ int indexOf(Task t) { - return mTaskIndices.get(t.key); + if (mTaskIndices.containsKey(t.key)) { + return mTaskIndices.get(t.key); + } + return -1; } /** Returns the size of the list of filtered tasks */ 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 07a7e74cfdab..1dd484bd36c6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -408,14 +408,26 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV int thumbnailHeight = transform.rect.height(); if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 && task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) { - // Resize the thumbnail to the size of the view that we are animating from - Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, - Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(b); - c.drawBitmap(task.thumbnail, - new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()), - new Rect(0, 0, thumbnailWidth, thumbnailHeight), null); - c.setBitmap(null); + Bitmap b; + if (tv != null) { + // Disable any focused state before we draw the header + if (tv.isFocusedTask()) { + tv.unsetFocusedTask(); + } + + b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, Bitmap.Config.ARGB_8888); + if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) { + b.eraseColor(0xFFff0000); + } else { + Canvas c = new Canvas(b); + c.scale(tv.getScaleX(), tv.getScaleY()); + tv.mHeaderView.draw(c); + c.setBitmap(null); + } + } else { + // Notify the system to skip the thumbnail layer by using an ALPHA_8 bitmap + b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, Bitmap.Config.ALPHA_8); + } ActivityOptions.OnAnimationStartedListener animStartedListener = null; if (lockToTask) { animStartedListener = new ActivityOptions.OnAnimationStartedListener() { @@ -434,7 +446,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } }; } - opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView, + opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView, b, offsetX, offsetY, animStartedListener); } @@ -472,7 +484,15 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV if (tv == null) { post(launchRunnable); } else { - stackView.startLaunchTaskAnimation(tv, launchRunnable); + if (!task.group.isFrontMostTask(task)) { + // For affiliated tasks that are behind other tasks, we must animate the front cards + // out of view before starting the task transition + stackView.startLaunchTaskAnimation(tv, launchRunnable); + } else { + // Otherwise, we can start the task transition immediately + stackView.startLaunchTaskAnimation(tv, null); + postDelayed(launchRunnable, 17); + } } } @@ -485,7 +505,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV intent.setComponent(intent.resolveActivity(getContext().getPackageManager())); TaskStackBuilder.create(getContext()) .addNextIntentWithParentStack(intent).startActivities(null, - new UserHandle(t.userId)); + new UserHandle(t.key.userId)); } @Override 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 aee558f88f65..861011fb3546 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -460,7 +460,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** - * This is called with the full window width and height to allow stack view children to + * This is ONLY used from AlternateRecentsComponent to update the dummy stack view for purposes + * of getting the task rect to animate to. + */ + public void updateMinMaxScrollForStack(TaskStack stack, boolean launchedWithAltTab, + boolean launchedFromHome) { + mStack = stack; + updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome); + } + + /** + * This is called with the full window width and height to allow stack view children to * perform the full screen transition down. */ @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java index f0bdfa2046e8..31fc7014db3a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java @@ -33,7 +33,7 @@ import java.util.HashMap; public class TaskStackViewLayoutAlgorithm { // These are all going to change - static final float StackPeekMinScale = 0.825f; // The min scale of the last card in the peek area + static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area RecentsConfiguration mConfig; @@ -157,7 +157,7 @@ public class TaskStackViewLayoutAlgorithm { public TaskViewTransform getStackTransform(Task task, float stackScroll, TaskViewTransform transformOut, TaskViewTransform prevTransform) { // Return early if we have an invalid index - if (task == null) { + if (task == null || !mTaskProgressMap.containsKey(task.key)) { transformOut.reset(); return transformOut; } @@ -200,6 +200,15 @@ public class TaskStackViewLayoutAlgorithm { } /** + * Returns the untransformed task view size. + */ + public Rect getUntransformedTaskViewSize() { + Rect tvSize = new Rect(mTaskRect); + tvSize.offsetTo(0, 0); + return tvSize; + } + + /** * Returns the scroll to such task top = 1f; */ float getStackScrollForTask(Task t) { 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 e514c90c0966..dfbcce1df8e4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -235,7 +235,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, int initialDim = getDim(); if (mConfig.launchedFromAppWithScreenshot) { if (isTaskViewLaunchTargetTask) { - mHeaderView.prepareEnterRecentsAnimation(); // Hide the footer during the transition in, and animate it out afterwards? if (mFooterView != null) { mFooterView.animateFooterVisibility(false, 0); @@ -246,8 +245,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, } else if (mConfig.launchedFromAppWithThumbnail) { if (isTaskViewLaunchTargetTask) { - // Hide the front most task bar view so we can animate it in - mHeaderView.prepareEnterRecentsAnimation(); // Hide the action button if it exists mActionButtonView.setAlpha(0f); // Set the dim to 0 so we can animate it in @@ -306,7 +303,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, mViewBounds.animateClipBottom(getMeasuredHeight() - (windowInsetTop + size), duration); } // Animate the task bar of the first task view - mHeaderView.startEnterRecentsAnimation(0, null); animate() .scaleX(taskScale) .scaleY(taskScale) @@ -352,9 +348,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, } else if (mConfig.launchedFromAppWithThumbnail) { if (mTask.isLaunchTarget) { - // Animate the task bar of the first task view - mHeaderView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay, - mThumbnailView.enableTaskBarClipAsRunnable(mHeaderView)); + // Enable the task bar clip + mThumbnailView.enableTaskBarClip(mHeaderView); // Animate the dim/overlay if (Constants.DebugFlags.App.EnableThumbnailAlphaOnFrontmost) { // Animate the thumbnail alpha before the dim animation (to prevent updating the @@ -475,14 +470,13 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, } /** Animates this task view as it exits recents */ - void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask, + void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask, boolean occludesLaunchTarget) { if (isLaunchingTask) { - // Disable the thumbnail clip and animate the bar out for the window animation out - mHeaderView.startLaunchTaskAnimation(mThumbnailView.disableTaskBarClipAsRunnable(), r, - mIsFocused); + // Disable the thumbnail clip + mThumbnailView.disableTaskBarClip(); // Animate the thumbnail alpha back into full opacity for the window animation out - mThumbnailView.startLaunchTaskAnimation(); + mThumbnailView.startLaunchTaskAnimation(postAnimRunnable); // Animate the dim if (mDim > 0) { @@ -493,7 +487,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, } // Animate the action button away - mActionButtonView.animate().alpha(0f) + float toScale = 0.9f; + mActionButtonView.animate() + .alpha(0f) + .scaleX(toScale) + .scaleY(toScale) .setStartDelay(0) .setDuration(mConfig.taskBarExitAnimDuration) .setInterpolator(mConfig.fastOutLinearInInterpolator) @@ -706,22 +704,30 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, } /** + * Unsets the focused task explicitly. + */ + void unsetFocusedTask() { + mIsFocused = false; + if (mFocusAnimationsEnabled) { + // Un-focus the header bar + mHeaderView.onTaskViewFocusChanged(false); + } + + // Update the thumbnail alpha with the focus + mThumbnailView.onFocusChanged(false); + // Call the callback + mCb.onTaskViewFocusChanged(this, false); + invalidate(); + } + + /** * Updates the explicitly focused state when the view focus changes. */ @Override protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); if (!gainFocus) { - mIsFocused = false; - if (mFocusAnimationsEnabled) { - // Un-focus the header bar - mHeaderView.onTaskViewFocusChanged(false); - } - // Update the thumbnail alpha with the focus - mThumbnailView.onFocusChanged(false); - // Call the callback - mCb.onTaskViewFocusChanged(this, false); - invalidate(); + unsetFocusedTask(); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index d39f64eec5d2..1743433a858e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -49,7 +49,7 @@ import com.android.systemui.recents.model.Task; /* The task bar view */ -class TaskViewHeader extends FrameLayout { +public class TaskViewHeader extends FrameLayout { RecentsConfiguration mConfig; @@ -156,8 +156,11 @@ class TaskViewHeader extends FrameLayout { // Draw the highlight at the top edge (but put the bottom edge just out of view) float offset = (float) Math.ceil(mConfig.taskViewHighlightPx / 2f); float radius = mConfig.taskViewRoundedCornerRadiusPx; + int count = canvas.save(Canvas.CLIP_SAVE_FLAG); + canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight()); canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset, getMeasuredHeight() + radius, radius, radius, sHighlightPaint); + canvas.restoreToCount(count); } } @@ -178,7 +181,7 @@ class TaskViewHeader extends FrameLayout { } /** Binds the bar view to the task */ - void rebindToTask(Task t) { + public void rebindToTask(Task 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) { @@ -212,51 +215,6 @@ class TaskViewHeader extends FrameLayout { mApplicationIcon.setImageDrawable(null); } - /** 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. */ - void prepareEnterRecentsAnimation() { - setVisibility(View.INVISIBLE); - } - - /** Animates this task bar as it enters recents */ - void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) { - // Animate the task bar of the first task view - setVisibility(View.VISIBLE); - setAlpha(0f); - animate() - .alpha(1f) - .setStartDelay(delay) - .setInterpolator(mConfig.linearOutSlowInInterpolator) - .setDuration(mConfig.taskBarEnterAnimDuration) - .withEndAction(postAnimRunnable) - .withLayer() - .start(); - } - - /** Animates this task bar as it exits recents */ - void startLaunchTaskAnimation(Runnable preAnimRunnable, final Runnable postAnimRunnable, - boolean isFocused) { - if (isFocused) { - onTaskViewFocusChanged(false); - } - - // Animate the task bar out of the first task view - animate() - .alpha(0f) - .setStartDelay(0) - .setInterpolator(mConfig.linearOutSlowInInterpolator) - .setDuration(mConfig.taskBarExitAnimDuration) - .withStartAction(preAnimRunnable) - .withEndAction(new Runnable() { - @Override - public void run() { - post(postAnimRunnable); - } - }) - .withLayer() - .start(); - } - /** Animates this task bar dismiss button when launching a task. */ void startLaunchTaskDismissAnimation() { if (mDismissButton.getVisibility() == View.VISIBLE) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java index f223bf3a990f..fe369879ad16 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java @@ -64,25 +64,10 @@ public class TaskViewThumbnail extends FixedSizeImageView { setClipBounds(mClipRect); } - /** 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. */ - Runnable disableTaskBarClipAsRunnable() { - return new Runnable() { - @Override - public void run() { - mClipRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); - setClipBounds(mClipRect); - } - }; + void disableTaskBarClip() { + mClipRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); + setClipBounds(mClipRect); } /** Binds the thumbnail view to the screenshot. */ @@ -140,8 +125,8 @@ public class TaskViewThumbnail extends FixedSizeImageView { } /** Animates this task thumbnail as it exits recents */ - void startLaunchTaskAnimation() { - startFadeAnimation(1f, 0, mConfig.taskBarExitAnimDuration, null); + void startLaunchTaskAnimation(Runnable postAnimRunnable) { + startFadeAnimation(1f, 0, mConfig.taskBarExitAnimDuration, postAnimRunnable); } /** Animates the thumbnail alpha. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarViewTaskSwitchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarViewTaskSwitchHelper.java index 3c20d1f32b8f..b87bec4545f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarViewTaskSwitchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarViewTaskSwitchHelper.java @@ -36,7 +36,7 @@ public class NavigationBarViewTaskSwitchHelper extends GestureDetector.SimpleOnG public NavigationBarViewTaskSwitchHelper(Context context) { ViewConfiguration configuration = ViewConfiguration.get(context); - mScrollTouchSlop = configuration.getScaledTouchSlop(); + mScrollTouchSlop = 4 * configuration.getScaledTouchSlop(); mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity(); mTaskSwitcherDetector = new GestureDetector(context, this); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f36f25f93d90..517e100291ee 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -7706,6 +7706,12 @@ public final class ActivityManagerService extends ActivityManagerNative continue; } } + if ((flags & ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS) != 0) { + if (tr.stack != null && tr.stack.isHomeStack()) { + if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, home stack task: " + tr); + continue; + } + } if (tr.autoRemoveRecents && tr.getTopActivity() == null) { // Don't include auto remove tasks that are finished or finishing. if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, auto-remove without activity: " diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index fcbe71ebb30b..39b6375dc4b9 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -723,6 +723,22 @@ final class ActivityRecord { + pendingOptions.getThumbnail().getHeight())); } break; + case ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP: + case ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_DOWN: + service.mWindowManager.overridePendingAppTransitionAspectScaledThumb( + pendingOptions.getThumbnail(), + pendingOptions.getStartX(), pendingOptions.getStartY(), + pendingOptions.getOnAnimationStartListener(), + (animationType == ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP)); + if (intent.getSourceBounds() == null) { + intent.setSourceBounds(new Rect(pendingOptions.getStartX(), + pendingOptions.getStartY(), + pendingOptions.getStartX() + + pendingOptions.getThumbnail().getWidth(), + pendingOptions.getStartY() + + pendingOptions.getThumbnail().getHeight())); + } + break; default: Slog.e(TAG, "applyOptionsLocked: Unknown animationType=" + animationType); break; diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index f6ec86db5478..012667510725 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -19,7 +19,6 @@ package com.android.server.wm; import android.content.Context; import android.content.res.Configuration; import android.graphics.Bitmap; -import android.graphics.Point; import android.graphics.Rect; import android.os.Debug; import android.os.Handler; @@ -111,11 +110,14 @@ public class AppTransition implements Dump { * The new window will show briefly and then be gone. */ public static final int TRANSIT_TASK_OPEN_BEHIND = 16; + /** Fraction of animation at which the recents thumbnail stays completely transparent */ + private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.7f; /** Fraction of animation at which the recents thumbnail becomes completely transparent */ - private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.25f; + private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.3f; private static final int DEFAULT_APP_TRANSITION_DURATION = 250; - private static final int THUMBNAIL_APP_TRANSITION_DURATION = 275; + private static final int THUMBNAIL_APP_TRANSITION_DURATION = 300; + private static final int THUMBNAIL_APP_TRANSITION_ALPHA_DURATION = 325; private final Context mContext; private final Handler mH; @@ -127,6 +129,8 @@ public class AppTransition implements Dump { private static final int NEXT_TRANSIT_TYPE_SCALE_UP = 2; private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP = 3; private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN = 4; + private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP = 5; + private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN = 6; private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE; // These are the possible states for the enter/exit activities during a thumbnail transition @@ -146,6 +150,7 @@ public class AppTransition implements Dump { private int mNextAppTransitionStartY; private int mNextAppTransitionStartWidth; private int mNextAppTransitionStartHeight; + private Rect mNextAppTransitionInsets = new Rect(); private Rect mTmpFromClipRect = new Rect(); private Rect mTmpToClipRect = new Rect(); @@ -158,7 +163,8 @@ public class AppTransition implements Dump { private final int mConfigShortAnimTime; private final Interpolator mDecelerateInterpolator; - private final Interpolator mThumbnailFadeoutInterpolator; + private final Interpolator mThumbnailFadeInInterpolator; + private final Interpolator mThumbnailFadeOutInterpolator; private final Interpolator mThumbnailFastOutSlowInInterpolator; private int mCurrentUserId = 0; @@ -172,14 +178,25 @@ public class AppTransition implements Dump { com.android.internal.R.interpolator.decelerate_cubic); mThumbnailFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.fast_out_slow_in); - mThumbnailFadeoutInterpolator = new Interpolator() { + mThumbnailFadeInInterpolator = new Interpolator() { + @Override + public float getInterpolation(float input) { + // Linear response for first fraction, then complete after that. + if (input < RECENTS_THUMBNAIL_FADEIN_FRACTION) { + return 0f; + } + return (input - RECENTS_THUMBNAIL_FADEIN_FRACTION) / + (1f - RECENTS_THUMBNAIL_FADEIN_FRACTION); + } + }; + mThumbnailFadeOutInterpolator = new Interpolator() { @Override public float getInterpolation(float input) { // Linear response for first fraction, then complete after that. if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) { return input / RECENTS_THUMBNAIL_FADEOUT_FRACTION; } - return 1.0f; + return 1f; } }; } @@ -233,9 +250,23 @@ public class AppTransition implements Dump { return mNextAppTransitionThumbnail; } - void getStartingPoint(Point outPoint) { - outPoint.x = mNextAppTransitionStartX; - outPoint.y = mNextAppTransitionStartY; + /** Returns whether the next thumbnail transition is aspect scaled up. */ + boolean isNextThumbnailTransitionAspectScaled() { + return mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP || + mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN; + } + + /** Returns whether the next thumbnail transition is scaling up. */ + boolean isNextThumbnailTransitionScaleUp() { + return mNextAppTransitionScaleUp; + } + + int getStartingX() { + return mNextAppTransitionStartX; + } + + int getStartingY() { + return mNextAppTransitionStartY; } void prepare() { @@ -372,7 +403,7 @@ public class AppTransition implements Dump { scale.setInterpolator(mDecelerateInterpolator); Animation alpha = new AlphaAnimation(0, 1); - alpha.setInterpolator(mThumbnailFadeoutInterpolator); + alpha.setInterpolator(mThumbnailFadeOutInterpolator); AnimationSet set = new AnimationSet(false); set.addAnimation(scale); @@ -417,7 +448,9 @@ public class AppTransition implements Dump { */ Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth, int appHeight, int duration, Interpolator interpolator) { - a.setDuration(duration); + if (duration > 0) { + a.setDuration(duration); + } a.setFillAfter(true); a.setInterpolator(interpolator); a.initialize(appWidth, appHeight, appWidth, appHeight); @@ -468,50 +501,73 @@ public class AppTransition implements Dump { * This animation runs for the thumbnail that gets cross faded with the enter/exit activity * when a thumbnail is specified with the activity options. */ - Animation createThumbnailScaleAnimationLocked(int appWidth, int appHeight, int transit) { + Animation createThumbnailAspectScaleAnimationLocked(int appWidth, int appHeight, + int deviceWidth, int transit) { Animation a; final int thumbWidthI = mNextAppTransitionThumbnail.getWidth(); final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; final int thumbHeightI = mNextAppTransitionThumbnail.getHeight(); final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; + float scaleW = deviceWidth / thumbWidth; + float unscaledWidth = deviceWidth; + float unscaledHeight = thumbHeight * scaleW; + float unscaledStartY = mNextAppTransitionStartY - (unscaledHeight - thumbHeight) / 2f; if (mNextAppTransitionScaleUp) { - // Animation for the thumbnail zooming from its initial size to the full screen - float scaleW = appWidth / thumbWidth; - float scaleH = appHeight / thumbHeight; - Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH, - computePivot(mNextAppTransitionStartX, 1 / scaleW), - computePivot(mNextAppTransitionStartY, 1 / scaleH)); - scale.setInterpolator(mDecelerateInterpolator); - + // Animation up from the thumbnail to the full screen + Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, + mNextAppTransitionStartX + (thumbWidth / 2f), + mNextAppTransitionStartY + (thumbHeight / 2f)); + scale.setInterpolator(mThumbnailFastOutSlowInInterpolator); + scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); Animation alpha = new AlphaAnimation(1, 0); - alpha.setInterpolator(mThumbnailFadeoutInterpolator); + alpha.setInterpolator(mThumbnailFadeOutInterpolator); + alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION); + Animation translate = new TranslateAnimation(0, 0, 0, -unscaledStartY + + mNextAppTransitionInsets.top); + translate.setInterpolator(mThumbnailFastOutSlowInInterpolator); + translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); // This AnimationSet uses the Interpolators assigned above. AnimationSet set = new AnimationSet(false); set.addAnimation(scale); set.addAnimation(alpha); + set.addAnimation(translate); a = set; } else { - // Animation for the thumbnail zooming down from the full screen to its final size - float scaleW = appWidth / thumbWidth; - float scaleH = appHeight / thumbHeight; - a = new ScaleAnimation(scaleW, 1, scaleH, 1, - computePivot(mNextAppTransitionStartX, 1 / scaleW), - computePivot(mNextAppTransitionStartY, 1 / scaleH)); - } + // Animation down from the full screen to the thumbnail + Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, + mNextAppTransitionStartX + (thumbWidth / 2f), + mNextAppTransitionStartY + (thumbHeight / 2f)); + scale.setInterpolator(mThumbnailFastOutSlowInInterpolator); + scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); + Animation alpha = new AlphaAnimation(0f, 1f); + alpha.setInterpolator(mThumbnailFadeInInterpolator); + alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION); + Animation translate = new TranslateAnimation(0, 0, -unscaledStartY + + mNextAppTransitionInsets.top, 0); + translate.setInterpolator(mThumbnailFastOutSlowInInterpolator); + translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); - return prepareThumbnailAnimation(a, appWidth, appHeight, transit); + // This AnimationSet uses the Interpolators assigned above. + AnimationSet set = new AnimationSet(false); + set.addAnimation(scale); + set.addAnimation(alpha); + set.addAnimation(translate); + a = set; + + } + return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, 0, + mThumbnailFastOutSlowInInterpolator); } /** * This alternate animation is created when we are doing a thumbnail transition, for the * activity that is leaving, and the activity that is entering. */ - Animation createAlternateThumbnailEnterExitAnimationLocked(int thumbTransitState, int appWidth, - int appHeight, int orientation, int transit, - Rect containingFrame, Rect contentInsets, - boolean isFullScreen) { + Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState, + int appWidth, int appHeight, int orientation, int transit, Rect containingFrame, + Rect contentInsets, boolean isFullScreen) { Animation a; final int thumbWidthI = mNextAppTransitionThumbnail.getWidth(); final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; @@ -524,7 +580,7 @@ public class AppTransition implements Dump { switch (thumbTransitState) { case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: { - // Entering app scales up with the thumbnail + // App window scaling up to become full screen if (orientation == Configuration.ORIENTATION_PORTRAIT) { // In portrait, we scale the width and clip to the top/left square scale = thumbWidth / appWidth; @@ -550,16 +606,15 @@ public class AppTransition implements Dump { mTmpFromClipRect.right = (mTmpFromClipRect.left + unscaledThumbWidth); mTmpToClipRect.set(containingFrame); } + mNextAppTransitionInsets.set(contentInsets); Animation scaleAnim = new ScaleAnimation(scale, 1, scale, 1, computePivot(mNextAppTransitionStartX, scale), computePivot(mNextAppTransitionStartY, scale)); - Animation alphaAnim = new AlphaAnimation(1, 1); Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect); Animation translateAnim = new TranslateAnimation(0, 0, -scaledTopDecor, 0); AnimationSet set = new AnimationSet(true); - set.addAnimation(alphaAnim); set.addAnimation(clipAnim); set.addAnimation(scaleAnim); set.addAnimation(translateAnim); @@ -567,26 +622,29 @@ public class AppTransition implements Dump { break; } case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: { - // Exiting app while the thumbnail is scaling up should fade + // Previous app window during the scale up if (transit == TRANSIT_WALLPAPER_INTRA_OPEN) { - // Fade out while bringing up selected activity. This keeps the - // current activity from showing through a launching wallpaper + // Fade out the source activity if we are animating to a wallpaper // activity. a = new AlphaAnimation(1, 0); } else { - // noop animation a = new AlphaAnimation(1, 1); } break; } case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: { - // Entering the other app, it should just be visible while we scale the thumbnail - // down above it - a = new AlphaAnimation(1, 1); + // Target app window during the scale down + if (transit == TRANSIT_WALLPAPER_INTRA_OPEN) { + // Fade in the destination activity if we are animating from a wallpaper + // activity. + a = new AlphaAnimation(0, 1); + } else { + a = new AlphaAnimation(1, 1); + } break; } case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: { - // Exiting the current app, the app should scale down with the thumbnail + // App window scaling down from full screen if (orientation == Configuration.ORIENTATION_PORTRAIT) { // In portrait, we scale the width and clip to the top/left square scale = thumbWidth / appWidth; @@ -612,16 +670,15 @@ public class AppTransition implements Dump { } mTmpToClipRect.right = (mTmpToClipRect.left + unscaledThumbWidth); } + mNextAppTransitionInsets.set(contentInsets); Animation scaleAnim = new ScaleAnimation(1, scale, 1, scale, computePivot(mNextAppTransitionStartX, scale), computePivot(mNextAppTransitionStartY, scale)); - Animation alphaAnim = new AlphaAnimation(1, 1); Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect); Animation translateAnim = new TranslateAnimation(0, 0, 0, -scaledTopDecor); AnimationSet set = new AnimationSet(true); - set.addAnimation(alphaAnim); set.addAnimation(clipAnim); set.addAnimation(scaleAnim); set.addAnimation(translateAnim); @@ -639,6 +696,46 @@ public class AppTransition implements Dump { } /** + * This animation runs for the thumbnail that gets cross faded with the enter/exit activity + * when a thumbnail is specified with the activity options. + */ + Animation createThumbnailScaleAnimationLocked(int appWidth, int appHeight, int transit) { + Animation a; + final int thumbWidthI = mNextAppTransitionThumbnail.getWidth(); + final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; + final int thumbHeightI = mNextAppTransitionThumbnail.getHeight(); + final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; + + if (mNextAppTransitionScaleUp) { + // Animation for the thumbnail zooming from its initial size to the full screen + float scaleW = appWidth / thumbWidth; + float scaleH = appHeight / thumbHeight; + Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH, + computePivot(mNextAppTransitionStartX, 1 / scaleW), + computePivot(mNextAppTransitionStartY, 1 / scaleH)); + scale.setInterpolator(mDecelerateInterpolator); + + Animation alpha = new AlphaAnimation(1, 0); + alpha.setInterpolator(mThumbnailFadeOutInterpolator); + + // This AnimationSet uses the Interpolators assigned above. + AnimationSet set = new AnimationSet(false); + set.addAnimation(scale); + set.addAnimation(alpha); + a = set; + } else { + // Animation for the thumbnail zooming down from the full screen to its final size + float scaleW = appWidth / thumbWidth; + float scaleH = appHeight / thumbHeight; + a = new ScaleAnimation(scaleW, 1, scaleH, 1, + computePivot(mNextAppTransitionStartX, 1 / scaleW), + computePivot(mNextAppTransitionStartY, 1 / scaleH)); + } + + return prepareThumbnailAnimation(a, appWidth, appHeight, transit); + } + + /** * This animation is created when we are doing a thumbnail transition, for the activity that is * leaving, and the activity that is entering. */ @@ -747,12 +844,26 @@ public class AppTransition implements Dump { mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN) { mNextAppTransitionScaleUp = (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP); - a = createAlternateThumbnailEnterExitAnimationLocked( + a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter), + appWidth, appHeight, transit); + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) { + String animName = mNextAppTransitionScaleUp ? + "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN"; + Slog.v(TAG, "applyAnimation:" + + " anim=" + a + " nextAppTransition=" + animName + + " transit=" + transit + " isEntrance=" + enter + + " Callers=" + Debug.getCallers(3)); + } + } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP || + mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN) { + mNextAppTransitionScaleUp = + (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP); + a = createAspectScaledThumbnailEnterExitAnimationLocked( getThumbnailTransitionState(enter), appWidth, appHeight, orientation, transit, containingFrame, contentInsets, isFullScreen); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) { String animName = mNextAppTransitionScaleUp ? - "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN"; + "ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN"; Slog.v(TAG, "applyAnimation:" + " anim=" + a + " nextAppTransition=" + animName + " transit=" + transit + " isEntrance=" + enter @@ -881,6 +992,23 @@ public class AppTransition implements Dump { } } + void overridePendingAppTransitionAspectScaledThumb(Bitmap srcThumb, int startX, int startY, + IRemoteCallback startedCallback, boolean scaleUp) { + if (isTransitionSet()) { + mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP + : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN; + mNextAppTransitionPackage = null; + mNextAppTransitionThumbnail = srcThumb; + mNextAppTransitionScaleUp = scaleUp; + mNextAppTransitionStartX = startX; + mNextAppTransitionStartY = startY; + postAnimationCallback(); + mNextAppTransitionCallback = startedCallback; + } else { + postAnimationCallback(); + } + } + @Override public String toString() { return "mNextAppTransition=0x" + Integer.toHexString(mNextAppTransition); @@ -966,6 +1094,10 @@ public class AppTransition implements Dump { return "NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP"; case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN: return "NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN"; + case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP: + return "NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP"; + case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN: + return "NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN"; default: return "unknown type=" + mNextAppTransitionType; } @@ -998,6 +1130,8 @@ public class AppTransition implements Dump { break; case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP: case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN: + case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP: + case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN: pw.print(" mNextAppTransitionThumbnail="); pw.print(mNextAppTransitionThumbnail); pw.print(" mNextAppTransitionStartX="); diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java index 5b05d534d1e6..ef742057f1b8 100644 --- a/services/core/java/com/android/server/wm/AppWindowAnimator.java +++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java @@ -64,8 +64,17 @@ public class AppWindowAnimator { int thumbnailX; int thumbnailY; int thumbnailLayer; + int thumbnailForceAboveLayer; Animation thumbnailAnimation; final Transformation thumbnailTransformation = new Transformation(); + // This flag indicates that the destruction of the thumbnail surface is synchronized with + // another animation, so do not pre-emptively destroy the thumbnail surface when the animation + // completes + boolean deferThumbnailDestruction; + // This is the thumbnail surface that has been bestowed upon this animator, and when the + // surface for this animator's animation is complete, we will destroy the thumbnail surface + // as well. Do not animate or do anything with this surface. + SurfaceControl deferredThumbnail; /** WindowStateAnimator from mAppAnimator.allAppWindows as of last performLayout */ ArrayList<WindowStateAnimator> mAllAppWinAnimators = new ArrayList<WindowStateAnimator>(); @@ -121,7 +130,9 @@ public class AppWindowAnimator { animation = null; animating = true; } - clearThumbnail(); + if (!deferThumbnailDestruction) { + clearThumbnail(); + } if (mAppToken.deferClearAllDrawn) { mAppToken.allDrawn = false; mAppToken.deferClearAllDrawn = false; @@ -135,6 +146,13 @@ public class AppWindowAnimator { } } + public void clearDeferredThumbnail() { + if (deferredThumbnail != null) { + deferredThumbnail.destroy(); + deferredThumbnail = null; + } + } + void updateLayers() { final int N = mAppToken.allAppWindows.size(); final int adj = animLayerAdjustment; @@ -184,10 +202,14 @@ public class AppWindowAnimator { + "][" + tmpFloats[Matrix.MSKEW_X] + "," + tmpFloats[Matrix.MSCALE_Y] + "]", null); thumbnail.setAlpha(thumbnailTransformation.getAlpha()); - // The thumbnail is layered below the window immediately above this - // token's anim layer. - thumbnail.setLayer(thumbnailLayer + WindowManagerService.WINDOW_LAYER_MULTIPLIER - - WindowManagerService.LAYER_OFFSET_THUMBNAIL); + if (thumbnailForceAboveLayer > 0) { + thumbnail.setLayer(thumbnailForceAboveLayer + 1); + } else { + // The thumbnail is layered below the window immediately above this + // token's anim layer. + thumbnail.setLayer(thumbnailLayer + WindowManagerService.WINDOW_LAYER_MULTIPLIER + - WindowManagerService.LAYER_OFFSET_THUMBNAIL); + } thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y], tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]); } @@ -202,7 +224,9 @@ public class AppWindowAnimator { TAG, "Stepped animation in " + mAppToken + ": more=" + more + ", xform=" + transformation); if (!more) { animation = null; - clearThumbnail(); + if (!deferThumbnailDestruction) { + clearThumbnail(); + } if (WindowManagerService.DEBUG_ANIM) Slog.v( TAG, "Finished animation in " + mAppToken + " @ " + currentTime); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 32f3707bcfaf..e144bdeb43a3 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -95,7 +95,6 @@ import android.os.WorkSource; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.EventLog; -import android.util.FloatMath; import android.util.Log; import android.util.SparseArray; import android.util.Pair; @@ -4038,6 +4037,15 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void overridePendingAppTransitionAspectScaledThumb(Bitmap srcThumb, int startX, + int startY, IRemoteCallback startedCallback, boolean scaleUp) { + synchronized(mWindowMap) { + mAppTransition.overridePendingAppTransitionAspectScaledThumb(srcThumb, startX, + startY, startedCallback, scaleUp); + } + } + + @Override public void executeAppTransition() { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "executeAppTransition()")) { @@ -8893,7 +8901,9 @@ public class WindowManagerService extends IWindowManager.Stub } AppWindowToken topOpeningApp = null; + AppWindowToken topClosingApp = null; int topOpeningLayer = 0; + int topClosingLayer = 0; NN = mOpeningApps.size(); for (i=0; i<NN; i++) { @@ -8901,8 +8911,8 @@ public class WindowManagerService extends IWindowManager.Stub final AppWindowAnimator appAnimator = wtoken.mAppAnimator; if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken); appAnimator.clearThumbnail(); - wtoken.inPendingTransaction = false; appAnimator.animation = null; + wtoken.inPendingTransaction = false; setTokenVisibilityLocked(wtoken, animLp, true, transit, false, voiceInteraction); wtoken.updateReportedVisibilityLocked(); wtoken.waitingToShow = false; @@ -8931,10 +8941,11 @@ public class WindowManagerService extends IWindowManager.Stub NN = mClosingApps.size(); for (i=0; i<NN; i++) { AppWindowToken wtoken = mClosingApps.valueAt(i); + final AppWindowAnimator appAnimator = wtoken.mAppAnimator; if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken); - wtoken.mAppAnimator.clearThumbnail(); + appAnimator.clearThumbnail(); + appAnimator.animation = null; wtoken.inPendingTransaction = false; - wtoken.mAppAnimator.animation = null; setTokenVisibilityLocked(wtoken, animLp, false, transit, false, voiceInteraction); wtoken.updateReportedVisibilityLocked(); wtoken.waitingToHide = false; @@ -8943,14 +8954,30 @@ public class WindowManagerService extends IWindowManager.Stub // gotten drawn. wtoken.allDrawn = true; wtoken.deferClearAllDrawn = false; + + if (animLp != null) { + int layer = -1; + for (int j=0; j<wtoken.windows.size(); j++) { + WindowState win = wtoken.windows.get(j); + if (win.mWinAnimator.mAnimLayer > layer) { + layer = win.mWinAnimator.mAnimLayer; + } + } + if (topClosingApp == null || layer > topClosingLayer) { + topClosingApp = wtoken; + topClosingLayer = layer; + } + } } - boolean useAlternateThumbnailAnimation = true; - AppWindowAnimator appAnimator = - topOpeningApp == null ? null : topOpeningApp.mAppAnimator; + AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ? null : + topOpeningApp.mAppAnimator; + AppWindowAnimator closingAppAnimator = (topClosingApp == null) ? null : + topClosingApp.mAppAnimator; Bitmap nextAppTransitionThumbnail = mAppTransition.getNextAppTransitionThumbnail(); - if (!useAlternateThumbnailAnimation && nextAppTransitionThumbnail != null - && appAnimator != null && appAnimator.animation != null) { + if (nextAppTransitionThumbnail != null + && openingAppAnimator != null && openingAppAnimator.animation != null && + nextAppTransitionThumbnail.getConfig() != Config.ALPHA_8) { // This thumbnail animation is very special, we need to have // an extra surface with the thumbnail included with the animation. Rect dirty = new Rect(0, 0, nextAppTransitionThumbnail.getWidth(), @@ -8959,34 +8986,61 @@ public class WindowManagerService extends IWindowManager.Stub // TODO(multi-display): support other displays final DisplayContent displayContent = getDefaultDisplayContentLocked(); final Display display = displayContent.getDisplay(); + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + + // Create a new surface for the thumbnail SurfaceControl surfaceControl = new SurfaceControl(mFxSession, - "thumbnail anim", - dirty.width(), dirty.height(), + "thumbnail anim", dirty.width(), dirty.height(), PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); surfaceControl.setLayerStack(display.getLayerStack()); - appAnimator.thumbnail = surfaceControl; - if (SHOW_TRANSACTIONS) Slog.i(TAG, " THUMBNAIL " + surfaceControl + ": CREATE"); + if (SHOW_TRANSACTIONS) { + Slog.i(TAG, " THUMBNAIL " + surfaceControl + ": CREATE"); + } + + // Draw the thumbnail onto the surface Surface drawSurface = new Surface(); drawSurface.copyFrom(surfaceControl); Canvas c = drawSurface.lockCanvas(dirty); c.drawBitmap(nextAppTransitionThumbnail, 0, 0, null); drawSurface.unlockCanvasAndPost(c); drawSurface.release(); - appAnimator.thumbnailLayer = topOpeningLayer; - DisplayInfo displayInfo = getDefaultDisplayInfoLocked(); - Animation anim = mAppTransition.createThumbnailScaleAnimationLocked( - displayInfo.appWidth, displayInfo.appHeight, transit); - appAnimator.thumbnailAnimation = anim; + + // Get the thumbnail animation + Animation anim; + if (mAppTransition.isNextThumbnailTransitionAspectScaled()) { + // For the new aspect-scaled transition, we want it to always show + // above the animating opening/closing window, and we want to + // synchronize its thumbnail surface with the surface for the + // open/close animation (only on the way down) + anim = mAppTransition.createThumbnailAspectScaleAnimationLocked( + displayInfo.appWidth, displayInfo.appHeight, + displayInfo.logicalWidth, transit); + openingAppAnimator.thumbnailForceAboveLayer = Math.max(topOpeningLayer, + topClosingLayer); + openingAppAnimator.deferThumbnailDestruction = + !mAppTransition.isNextThumbnailTransitionScaleUp(); + if (openingAppAnimator.deferThumbnailDestruction) { + if (closingAppAnimator != null && + closingAppAnimator.animation != null) { + closingAppAnimator.deferredThumbnail = surfaceControl; + } + } + } else { + anim = mAppTransition.createThumbnailScaleAnimationLocked( + displayInfo.appWidth, displayInfo.appHeight, transit); + } anim.restrictDuration(MAX_ANIMATION_DURATION); anim.scaleCurrentDuration(getTransitionAnimationScaleLocked()); - Point p = new Point(); - mAppTransition.getStartingPoint(p); - appAnimator.thumbnailX = p.x; - appAnimator.thumbnailY = p.y; + + openingAppAnimator.thumbnail = surfaceControl; + openingAppAnimator.thumbnailLayer = topOpeningLayer; + openingAppAnimator.thumbnailAnimation = anim; + openingAppAnimator.thumbnailX = mAppTransition.getStartingX(); + openingAppAnimator.thumbnailY = mAppTransition.getStartingY(); } catch (OutOfResourcesException e) { Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w=" + dirty.width() + " h=" + dirty.height(), e); - appAnimator.clearThumbnail(); + openingAppAnimator.clearThumbnail(); } } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 7bf090a35b99..38433ae9233b 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -902,6 +902,11 @@ class WindowStateAnimator { mWin.mHasSurface = false; mDrawState = NO_SURFACE; } + + // Destroy any deferred thumbnail surfaces + if (mAppAnimator != null) { + mAppAnimator.clearDeferredThumbnail(); + } } void destroyDeferredSurfaceLocked() { diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index f8bc2ae276c0..2604e970a42f 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -221,6 +221,12 @@ public class IWindowManagerImpl implements IWindowManager { } @Override + public void overridePendingAppTransitionAspectScaledThumb(Bitmap srcThumb, int startX, + int startY, IRemoteCallback startedCallback, boolean scaleUp) { + // TODO Auto-generated method stub + } + + @Override public void pauseKeyDispatching(IBinder arg0) throws RemoteException { // TODO Auto-generated method stub |