diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar index a76f4f9e0e..ef50ac4326 100644 Binary files a/quickstep/libs/sysui_shared.jar and b/quickstep/libs/sysui_shared.jar differ diff --git a/quickstep/res/drawable/task_thumbnail_background.xml b/quickstep/res/drawable/task_thumbnail_background.xml index 27efd6c68e..603380e753 100644 --- a/quickstep/res/drawable/task_thumbnail_background.xml +++ b/quickstep/res/drawable/task_thumbnail_background.xml @@ -15,5 +15,5 @@ --> - + diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml index 521551c217..a8b91c5d9d 100644 --- a/quickstep/res/layout/overview_panel.xml +++ b/quickstep/res/layout/overview_panel.xml @@ -19,35 +19,8 @@ android:theme="@style/HomeScreenElementTheme" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingTop="20dp" - android:paddingBottom="20dp" + android:layout_gravity="center" + android:clipChildren="false" android:clipToPadding="false" - android:layout_gravity="center_horizontal|bottom" - android:gravity="top"> - - - - - - - - - - - \ No newline at end of file + android:alpha="0.0" + android:visibility="invisible" /> \ No newline at end of file diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml index 9d8aea7111..fdf1adcbc9 100644 --- a/quickstep/res/layout/task.xml +++ b/quickstep/res/layout/task.xml @@ -23,13 +23,14 @@ android:id="@+id/snapshot" android:layout_width="match_parent" android:layout_height="match_parent" + android:layout_marginTop="24dp" android:scaleType="matrix" android:background="@drawable/task_thumbnail_background" android:elevation="4dp" /> \ No newline at end of file diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java index 9bdd7a3849..26f5d5b51b 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java @@ -46,12 +46,14 @@ public class OverviewState extends LauncherState { @Override public void onStateEnabled(Launcher launcher) { - ((RecentsView) launcher.getOverviewPanel()).setViewVisible(true); + RecentsView rv = launcher.getOverviewPanel(); + rv.setOverviewStateEnabled(true); } @Override public void onStateDisabled(Launcher launcher) { - ((RecentsView) launcher.getOverviewPanel()).setViewVisible(false); + RecentsView rv = launcher.getOverviewPanel(); + rv.setOverviewStateEnabled(false); } @Override diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java index caeef50778..0810579503 100644 --- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java +++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java @@ -40,8 +40,15 @@ import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.states.InternalStateHandler; +import com.android.launcher3.uioverrides.OverviewState; +import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan; import com.android.systemui.shared.recents.model.Task.TaskKey; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.BackgroundExecutor; +import com.android.systemui.shared.system.WindowManagerWrapper; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; @TargetApi(Build.VERSION_CODES.O) public class NavBarSwipeInteractionHandler extends InternalStateHandler implements FrameCallback { @@ -66,13 +73,15 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler implemen private static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f; + private final Rect mStableInsets = new Rect(); private final Rect mSourceRect = new Rect(); private final Rect mTargetRect = new Rect(); private final Rect mCurrentRect = new Rect(); private final RectEvaluator mRectEvaluator = new RectEvaluator(mCurrentRect); private final Bitmap mTaskSnapshot; - private final RunningTaskInfo mTaskInfo; + private final int mRunningTaskId; + private Future mFutureLoadPlan; private Launcher mLauncher; private Choreographer mChoreographer; @@ -94,9 +103,10 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler implemen private boolean mTouchEnded = false; private float mEndVelocity; - NavBarSwipeInteractionHandler(Bitmap taskSnapShot, RunningTaskInfo taskInfo) { + NavBarSwipeInteractionHandler(Bitmap taskSnapShot, RunningTaskInfo runningTaskInfo) { mTaskSnapshot = taskSnapShot; - mTaskInfo = taskInfo; + mRunningTaskId = runningTaskInfo.id; + WindowManagerWrapper.getInstance().getStableInsets(mStableInsets); } @Override @@ -109,22 +119,39 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler implemen } @Override - public void onNewIntent(Launcher launcher) { + public void onCreate(Launcher launcher) { mLauncher = launcher; - - // Go immediately - launcher.getStateManager().goToState(LauncherState.OVERVIEW, false); - - // Optimization - launcher.getAppsView().setVisibility(View.GONE); - - mDragView = new SnapshotDragView(launcher, mTaskSnapshot); - launcher.getDragLayer().addView(mDragView); + mDragView = new SnapshotDragView(mLauncher, mTaskSnapshot); + mLauncher.getDragLayer().addView(mDragView); mDragView.setPivotX(0); mDragView.setPivotY(0); - mRecentsView = launcher.getOverviewPanel(); - mRecentsView.scrollTo(0, 0); - mHotseat = launcher.getHotseat(); + mRecentsView = mLauncher.getOverviewPanel(); + mHotseat = mLauncher.getHotseat(); + + // Optimization + mLauncher.getAppsView().setVisibility(View.GONE); + + // Launch overview + mRecentsView.update(consumeLastLoadPlan()); + mLauncher.getStateManager().goToState(LauncherState.OVERVIEW, false /* animate */); + } + + @Override + public void onNewIntent(Launcher launcher, boolean alreadyOnHome) { + mLauncher = launcher; + mDragView = new SnapshotDragView(mLauncher, mTaskSnapshot); + mLauncher.getDragLayer().addView(mDragView); + mDragView.setPivotX(0); + mDragView.setPivotY(0); + mRecentsView = mLauncher.getOverviewPanel(); + mHotseat = mLauncher.getHotseat(); + + // Optimization + mLauncher.getAppsView().setVisibility(View.GONE); + + // Launch overview, animate if already on home + mRecentsView.update(consumeLastLoadPlan()); + mLauncher.getStateManager().goToState(LauncherState.OVERVIEW, alreadyOnHome); } @BinderThread @@ -167,19 +194,46 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler implemen // Init target rect. View targetView = ((ViewGroup) mRecentsView.getChildAt(0)).getChildAt(0); dl.getViewRectRelativeToSelf(targetView, mTargetRect); + mTargetRect.right = mTargetRect.left + mTargetRect.width(); + mTargetRect.bottom = mTargetRect.top + mTargetRect.height(); mSourceRect.set(0, 0, dl.getWidth(), dl.getHeight()); } - mCurrentShift = shift; - int hotseatHeight = mHotseat.getHeight(); - mHotseat.setTranslationY((1 - shift) * hotseatHeight); + if (!mSourceRect.isEmpty()) { + mCurrentShift = shift; + int hotseatHeight = mHotseat.getHeight(); + mHotseat.setTranslationY((1 - shift) * hotseatHeight); - mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect); + mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect); - mDragView.setTranslationX(mCurrentRect.left); - mDragView.setTranslationY(mCurrentRect.top); - mDragView.setScaleX((float) mCurrentRect.width() / mSourceRect.width()); - mDragView.setScaleY((float) mCurrentRect.width() / mSourceRect.width()); + float scale = (float) mCurrentRect.width() / mSourceRect.width(); + mDragView.setTranslationX(mCurrentRect.left - mStableInsets.left * scale * shift); + mDragView.setTranslationY(mCurrentRect.top - mStableInsets.top * scale * shift); + mDragView.setScaleX(scale); + mDragView.setScaleY(scale); + mDragView.getViewBounds().setClipTop((int) (mStableInsets.top * shift)); + mDragView.getViewBounds().setClipBottom((int) (mStableInsets.bottom * shift)); + } + } + + void setLastLoadPlan(Future futureLoadPlan) { + if (mFutureLoadPlan != null) { + mFutureLoadPlan.cancel(true); + } + mFutureLoadPlan = futureLoadPlan; + } + + private RecentsTaskLoadPlan consumeLastLoadPlan() { + try { + if (mFutureLoadPlan != null) { + return mFutureLoadPlan.get(); + } + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } finally { + mFutureLoadPlan = null; + } + return null; } @UiThread @@ -229,9 +283,8 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler implemen mHotseat.setTranslationY(0); mLauncher.setOnResumeCallback(() -> mDragView.close(false)); - // TODO: Task key should be received from Recents model - TaskKey taskKey = new TaskKey(mTaskInfo.id, 0, null, UserHandle.myUserId(), 0); - ActivityManagerWrapper.getInstance() - .startActivityFromRecentsAsync(taskKey, null, null, null); + // TODO: For now, assume that the task stack will have loaded in the bg, will update + // the lib api later for direct call + mRecentsView.launchTaskWithId(mRunningTaskId); } } diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java index d7559daad9..528b11d590 100644 --- a/quickstep/src/com/android/quickstep/RecentsView.java +++ b/quickstep/src/com/android/quickstep/RecentsView.java @@ -19,17 +19,43 @@ package com.android.quickstep; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; -import android.widget.HorizontalScrollView; +import android.view.LayoutInflater; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.Insettable; import com.android.launcher3.Launcher; +import com.android.launcher3.PagedView; import com.android.launcher3.R; +import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan; +import com.android.systemui.shared.recents.model.RecentsTaskLoader; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.TaskStack; +import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.TaskStackChangeListener; +import com.android.systemui.shared.system.WindowManagerWrapper; + +import java.util.ArrayList; /** - * A placeholder view for recents + * A list of recent tasks. */ -public class RecentsView extends HorizontalScrollView implements Insettable { +public class RecentsView extends PagedView { + + private boolean mOverviewStateEnabled; + private boolean mTaskStackListenerRegistered; + + private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { + @Override + public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) { + for (int i = 0; i < getChildCount(); i++) { + final TaskView taskView = (TaskView) getChildAt(i); + if (taskView.getTask().key.id == taskId) { + taskView.getThumbnail().setThumbnail(snapshot); + return; + } + } + } + }; public RecentsView(Context context) { this(context, null); @@ -41,24 +67,103 @@ public class RecentsView extends HorizontalScrollView implements Insettable { public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - setAlpha(0); - setVisibility(INVISIBLE); + setWillNotDraw(false); + setPageSpacing((int) getResources().getDimension(R.dimen.recents_page_spacing)); } - public void setViewVisible(boolean isVisible) { } + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + // TODO: These are rough calculations which currently use the stable insets + DeviceProfile profile = Launcher.getLauncher(getContext()).getDeviceProfile(); + Rect stableInsets = new Rect(); + WindowManagerWrapper.getInstance().getStableInsets(stableInsets); + Rect padding = profile.getWorkspacePadding(null); + float taskWidth = profile.getCurrentWidth() - stableInsets.left - stableInsets.right; + float taskHeight = profile.getCurrentHeight() - stableInsets.top - stableInsets.bottom; + float overviewHeight = profile.availableHeightPx - padding.top - padding.bottom + - stableInsets.top; + float overviewWidth = taskWidth * overviewHeight / taskHeight; + padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2); + setPadding(padding.left, padding.top, padding.right, padding.bottom); + } @Override - public void setInsets(Rect insets) { - MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); - lp.topMargin = insets.top; - lp.bottomMargin = insets.bottom; - lp.leftMargin = insets.left; - lp.rightMargin = insets.right; + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + updateTaskStackListenerState(); + } - DeviceProfile dp = Launcher.getLauncher(getContext()).getDeviceProfile(); - if (!dp.isVerticalBarLayout()) { - lp.bottomMargin += dp.hotseatBarSizePx + getResources().getDimensionPixelSize( - R.dimen.dynamic_grid_min_page_indicator_size); + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + updateTaskStackListenerState(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + updateTaskStackListenerState(); + } + + public void setOverviewStateEnabled(boolean enabled) { + mOverviewStateEnabled = enabled; + updateTaskStackListenerState(); + } + + public void update(RecentsTaskLoadPlan loadPlan) { + final RecentsTaskLoader loader = TouchInteractionService.getRecentsTaskLoader(); + setCurrentPage(0); + TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null; + if (stack == null) { + removeAllViews(); + return; + } + + // Ensure there are as many views as there are tasks in the stack (adding and trimming as + // necessary) + final LayoutInflater inflater = LayoutInflater.from(getContext()); + final ArrayList tasks = stack.getTasks(); + for (int i = getChildCount(); i < tasks.size(); i++) { + final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false); + addView(taskView); + } + while (getChildCount() > tasks.size()) { + final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1); + removeView(taskView); + loader.unloadTaskData(taskView.getTask()); + } + + // Rebind all task views + for (int i = tasks.size() - 1; i >= 0; i--) { + final Task task = tasks.get(i); + final TaskView taskView = (TaskView) getChildAt(tasks.size() - i - 1); + taskView.bind(task); + loader.loadTaskData(task); + } + } + + public void launchTaskWithId(int taskId) { + for (int i = 0; i < getChildCount(); i++) { + final TaskView taskView = (TaskView) getChildAt(i); + if (taskView.getTask().key.id == taskId) { + taskView.launchTask(false /* animate */); + return; + } + } + } + + private void updateTaskStackListenerState() { + boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow() + && getWindowVisibility() == VISIBLE; + if (registerStackListener != mTaskStackListenerRegistered) { + if (registerStackListener) { + ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); + } else { + ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener); + } + mTaskStackListenerRegistered = registerStackListener; } } } diff --git a/quickstep/src/com/android/quickstep/SnapshotDragView.java b/quickstep/src/com/android/quickstep/SnapshotDragView.java index 791fe9ff17..2ef39422d2 100644 --- a/quickstep/src/com/android/quickstep/SnapshotDragView.java +++ b/quickstep/src/com/android/quickstep/SnapshotDragView.java @@ -23,6 +23,7 @@ import android.view.MotionEvent; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Insettable; import com.android.launcher3.Launcher; +import com.android.systemui.shared.recents.view.AnimateableViewBounds; /** * Floating view which shows the task snapshot allowing it to be dragged and placed. @@ -31,12 +32,20 @@ public class SnapshotDragView extends AbstractFloatingView implements Insettable private final Launcher mLauncher; private final Bitmap mSnapshot; + private final AnimateableViewBounds mViewBounds; public SnapshotDragView(Launcher launcher, Bitmap snapshot) { super(launcher, null); mLauncher = launcher; mSnapshot = snapshot; + mViewBounds = new AnimateableViewBounds(this, 0); setWillNotDraw(false); + setClipToOutline(true); + setOutlineProvider(mViewBounds); + } + + AnimateableViewBounds getViewBounds() { + return mViewBounds; } @Override diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/TaskThumbnailView.java index 96c93c23f9..55d22e0323 100644 --- a/quickstep/src/com/android/quickstep/TaskThumbnailView.java +++ b/quickstep/src/com/android/quickstep/TaskThumbnailView.java @@ -22,20 +22,17 @@ import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.ColorMatrixColorFilter; import android.graphics.LightingColorFilter; import android.graphics.Matrix; -import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Shader; import android.util.AttributeSet; -import android.view.Display; -import android.view.View; -import android.view.ViewOutlineProvider; import android.widget.FrameLayout; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Launcher; import com.android.systemui.shared.recents.model.ThumbnailData; /** @@ -95,6 +92,14 @@ public class TaskThumbnailView extends FrameLayout { } } + /** + * Sets the alpha of the dim layer on top of this view. + */ + public void setDimAlpha(float dimAlpha) { + mDimAlpha = dimAlpha; + updateThumbnailPaintFilter(); + } + @Override protected void onDraw(Canvas canvas) { int viewWidth = getMeasuredWidth(); @@ -105,43 +110,40 @@ public class TaskThumbnailView extends FrameLayout { (int) (mThumbnailRect.height() * mThumbnailScale)); if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) { - int topOffset = 0; // Draw the background, there will be some small overdraw with the thumbnail if (thumbnailWidth < viewWidth) { // Portrait thumbnail on a landscape task view - canvas.drawRect(Math.max(0, thumbnailWidth), topOffset, viewWidth, viewHeight, + canvas.drawRect(Math.max(0, thumbnailWidth), 0, viewWidth, viewHeight, mBgFillPaint); } if (thumbnailHeight < viewHeight) { // Landscape thumbnail on a portrait task view - canvas.drawRect(0, Math.max(topOffset, thumbnailHeight), viewWidth, viewHeight, + canvas.drawRect(0, Math.max(0, thumbnailHeight), viewWidth, viewHeight, mBgFillPaint); } // Draw the thumbnail - canvas.drawRect(0, topOffset, thumbnailWidth, thumbnailHeight, mDrawPaint); + canvas.drawRect(0, 0, thumbnailWidth, thumbnailHeight, mDrawPaint); } else { canvas.drawRect(0, 0, viewWidth, viewHeight, mBgFillPaint); } } - void updateThumbnailPaintFilter() { - int mul = (int) ((1.0f - mDimAlpha) * 255); + private void updateThumbnailPaintFilter() { + int mul = (int) (mDimAlpha * 255); if (mBitmapShader != null) { - mLightingColorFilter = new LightingColorFilter(Color.WHITE, - Color.argb(255, mul, mul, mul)); + mLightingColorFilter = new LightingColorFilter(Color.argb(255, mul, mul, mul), 0); mDrawPaint.setColorFilter(mLightingColorFilter); mDrawPaint.setColor(0xFFffffff); mBgFillPaint.setColorFilter(mLightingColorFilter); } else { - int grey = mul; mDrawPaint.setColorFilter(null); - mDrawPaint.setColor(Color.argb(255, grey, grey, grey)); + mDrawPaint.setColor(Color.argb(255, mul, mul, mul)); } invalidate(); } - public void updateThumbnailMatrix() { + private void updateThumbnailMatrix() { mThumbnailScale = 1f; if (mBitmapShader != null && mThumbnailData != null) { if (getMeasuredWidth() == 0) { @@ -152,8 +154,7 @@ public class TaskThumbnailView extends FrameLayout { float invThumbnailScale = 1f / mThumbnailScale; final Configuration configuration = getContext().getApplicationContext().getResources().getConfiguration(); - final Point displaySize = new Point(); - getDisplay().getRealSize(displaySize); + final DeviceProfile profile = Launcher.getLauncher(getContext()).getDeviceProfile(); if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) { // If we are in the same orientation as the screenshot, just scale it to the @@ -163,23 +164,17 @@ public class TaskThumbnailView extends FrameLayout { // Scale the landscape thumbnail up to app size, then scale that to the task // view size to match other portrait screenshots mThumbnailScale = invThumbnailScale * - ((float) getMeasuredWidth() / displaySize.x); + ((float) getMeasuredWidth() / profile.getCurrentWidth()); } } else { // Otherwise, scale the screenshot to fit 1:1 in the current orientation mThumbnailScale = invThumbnailScale; } } - mMatrix.setTranslate(-mThumbnailData.insets.left * mThumbnailScale, - -mThumbnailData.insets.top * mThumbnailScale); + mMatrix.setTranslate(-mThumbnailData.insets.left, -mThumbnailData.insets.top); mMatrix.postScale(mThumbnailScale, mThumbnailScale); mBitmapShader.setLocalMatrix(mMatrix); } invalidate(); } - - public void setDimAlpha(float dimAlpha) { - mDimAlpha = dimAlpha; - updateThumbnailPaintFilter(); - } } diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java index ea584f0095..f6408a89d3 100644 --- a/quickstep/src/com/android/quickstep/TaskView.java +++ b/quickstep/src/com/android/quickstep/TaskView.java @@ -16,17 +16,26 @@ package com.android.quickstep; +import android.app.ActivityOptions; import android.content.Context; +import android.graphics.Rect; import android.util.AttributeSet; import android.widget.FrameLayout; import android.widget.ImageView; import com.android.launcher3.R; +import com.android.launcher3.uioverrides.OverviewState; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskCallbacks; import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat; +import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; +import com.android.systemui.shared.recents.view.RecentsTransition; import com.android.systemui.shared.system.ActivityManagerWrapper; +import java.util.ArrayList; +import java.util.List; + /** * A task in the Recents view. */ @@ -48,10 +57,7 @@ public class TaskView extends FrameLayout implements TaskCallbacks { super(context, attrs, defStyleAttr); setWillNotDraw(false); setOnClickListener((view) -> { - if (mTask != null) { - ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key, - null, null, null); - } + launchTask(true /* animate */); }); } @@ -65,20 +71,63 @@ public class TaskView extends FrameLayout implements TaskCallbacks { * Updates this task view to the given {@param task}. */ public void bind(Task task) { + if (mTask != null) { + mTask.removeCallback(this); + } mTask = task; task.addCallback(this); } + public Task getTask() { + return mTask; + } + + public TaskThumbnailView getThumbnail() { + return mSnapshotView; + } + + public void launchTask(boolean animate) { + if (mTask != null) { + final ActivityOptions opts; + if (animate) { + // Calculate the bounds of the thumbnail to animate from + final Rect bounds = new Rect(); + final int[] pos = new int[2]; + mSnapshotView.getLocationInWindow(pos); + bounds.set(pos[0], pos[1], + pos[0] + mSnapshotView.getWidth(), + pos[1] + mSnapshotView.getHeight()); + AppTransitionAnimationSpecsFuture animFuture = + new AppTransitionAnimationSpecsFuture(getHandler()) { + @Override + public List composeSpecs() { + ArrayList specs = + new ArrayList<>(); + specs.add(new AppTransitionAnimationSpecCompat(mTask.key.id, null, + bounds)); + return specs; + } + }; + opts = RecentsTransition.createAspectScaleAnimation( + getContext(), getHandler(), true /* scaleUp */, animFuture, null); + } else { + opts = ActivityOptions.makeCustomAnimation(getContext(), 0, 0); + } + ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key, + opts, null, null); + } + } + @Override public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) { mSnapshotView.setThumbnail(thumbnailData); - mSnapshotView.setDimAlpha(1f); mIconView.setImageDrawable(task.icon); } @Override public void onTaskDataUnloaded() { - // Do nothing + mSnapshotView.setThumbnail(null); + mIconView.setImageDrawable(null); } @Override diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index dcfa53b41e..121817698c 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -23,8 +23,10 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityOptions; import android.app.Service; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.PointF; @@ -32,6 +34,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import android.view.Display; import android.view.MotionEvent; @@ -39,9 +42,17 @@ import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.view.WindowManager; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.R; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; +import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan; +import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.Options; +import com.android.systemui.shared.recents.model.RecentsTaskLoader; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.BackgroundExecutor; + +import java.util.concurrent.Future; /** * Service connected by system-UI for handling touch interaction. @@ -50,6 +61,8 @@ public class TouchInteractionService extends Service { private static final String TAG = "TouchInteractionService"; + private static RecentsTaskLoader sRecentsTaskLoader; + private final IBinder mMyBinder = new IOverviewProxy.Stub() { @Override @@ -68,6 +81,7 @@ public class TouchInteractionService extends Service { private Intent mHomeIntent; private ComponentName mLauncher; + private final PointF mDownPos = new PointF(); private final PointF mLastPos = new PointF(); private int mActivePointerId = INVALID_POINTER_ID; @@ -89,6 +103,14 @@ public class TouchInteractionService extends Service { ResolveInfo info = getPackageManager().resolveActivity(mHomeIntent, 0); mLauncher = new ComponentName(getPackageName(), info.activityInfo.name); mHomeIntent.setComponent(mLauncher); + + Resources res = getResources(); + if (sRecentsTaskLoader == null) { + sRecentsTaskLoader = new RecentsTaskLoader(this, + res.getInteger(R.integer.config_recentsMaxThumbnailCacheSize), + res.getInteger(R.integer.config_recentsMaxIconCacheSize), 0); + sRecentsTaskLoader.startLoader(this); + } } @Override @@ -97,6 +119,10 @@ public class TouchInteractionService extends Service { return mMyBinder; } + public static RecentsTaskLoader getRecentsTaskLoader() { + return sRecentsTaskLoader; + } + private void handleMotionEvent(MotionEvent ev) { if (ev.getActionMasked() != MotionEvent.ACTION_DOWN && mVelocityTracker == null) { return; @@ -170,16 +196,46 @@ public class TouchInteractionService extends Service { } private void startTouchTracking() { - mInteractionHandler = new NavBarSwipeInteractionHandler(getCurrentTaskSnapshot(), mRunningTask); + // Create the shared handler + mInteractionHandler = new NavBarSwipeInteractionHandler(getCurrentTaskSnapshot(), + mRunningTask); - Bundle extras = new Bundle(); - extras.putBinder(EXTRA_STATE_HANDLER, mInteractionHandler); - Intent homeIntent = new Intent(mHomeIntent).putExtras(extras); + // Preload and start the recents activity on a background thread + final Context context = this; + final int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().id; + final RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(context); + Future loadPlanFuture = BackgroundExecutor.get().submit(() -> { + // Preload the plan + RecentsTaskLoader loader = TouchInteractionService.getRecentsTaskLoader(); + loadPlan.preloadPlan(loader, runningTaskId, UserHandle.myUserId()); - // TODO: Call ActivityManager#startRecentsActivity instead, so that the system knows that - // recents was started and not Home. - startActivity(homeIntent, - ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle()); + // Pass the + Bundle extras = new Bundle(); + extras.putBinder(EXTRA_STATE_HANDLER, mInteractionHandler); + + // Start the activity + Intent homeIntent = new Intent(mHomeIntent); + homeIntent.putExtras(extras); + startActivity(homeIntent, ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle()); + /* + ActivityManagerWrapper.getInstance().startRecentsActivity(null, options, + ActivityOptions.makeCustomAnimation(this, 0, 0), UserHandle.myUserId(), + null, null); + */ + + // Kick off loading of the plan while the activity is starting + Options loadOpts = new Options(); + loadOpts.runningTaskId = runningTaskId; + loadOpts.loadIcons = true; + loadOpts.loadThumbnails = true; + loadOpts.numVisibleTasks = 2; + loadOpts.numVisibleTaskThumbnails = 2; + loadOpts.onlyLoadForCache = false; + loadOpts.onlyLoadPausedActivities = false; + loader.loadTasks(loadPlan, loadOpts); + }, loadPlan); + + mInteractionHandler.setLastLoadPlan(loadPlanFuture); } private void endInteraction() { diff --git a/res/values/config.xml b/res/values/config.xml index 54328e1486..0ff0d758bf 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -134,4 +134,8 @@ + + 6 + 12 + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 957ec191cb..59736d8fde 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -232,4 +232,7 @@ 19dp 0.5dp 70dp + + + 10dp diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 6030e08865..6ddaf29db8 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -27,7 +27,6 @@ import android.graphics.Rect; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.View; -import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.FrameLayout; @@ -681,13 +680,13 @@ public class DeviceProfile { } } - private int getCurrentWidth() { + public int getCurrentWidth() { return isLandscape ? Math.max(widthPx, heightPx) : Math.min(widthPx, heightPx); } - private int getCurrentHeight() { + public int getCurrentHeight() { return isLandscape ? Math.min(widthPx, heightPx) : Math.max(widthPx, heightPx); diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index b1b3452e4b..60c00fb55b 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -63,7 +63,6 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.AsyncTask; -import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Parcelable; @@ -362,6 +361,8 @@ public class Launcher extends BaseActivity restoreState(savedInstanceState); + InternalStateHandler.handleCreate(this, getIntent()); + // We only load the page synchronously if the user rotates (or triggers a // configuration change) while launcher is in the foreground int currentScreen = PagedView.INVALID_RESTORE_PAGE; @@ -1347,7 +1348,6 @@ public class Launcher extends BaseActivity // Check this condition before handling isActionMain, as this will get reset. boolean shouldMoveToDefaultScreen = alreadyOnHome && isInState(NORMAL) && AbstractFloatingView.getTopOpenView(this) == null; - boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction()); if (isActionMain) { if (mWorkspace == null) { @@ -1407,7 +1407,7 @@ public class Launcher extends BaseActivity }); } } - InternalStateHandler.handleIntent(this, intent); + InternalStateHandler.handleNewIntent(this, intent, alreadyOnHome); TraceHelper.endSection("NEW_INTENT"); } diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java index a90ed36aa9..c6feefc8bc 100644 --- a/src/com/android/launcher3/states/InternalStateHandler.java +++ b/src/com/android/launcher3/states/InternalStateHandler.java @@ -29,15 +29,30 @@ public abstract class InternalStateHandler extends Binder implements OnResumeCal public static final String EXTRA_STATE_HANDLER = "launcher.state_handler"; - public abstract void onNewIntent(Launcher launcher); + public abstract void onCreate(Launcher launcher); + public abstract void onNewIntent(Launcher launcher, boolean alreadyOnHome); - public static void handleIntent(Launcher launcher, Intent intent) { - IBinder stateBinder = intent.getExtras().getBinder(EXTRA_STATE_HANDLER); - if (stateBinder instanceof InternalStateHandler) { - InternalStateHandler handler = (InternalStateHandler) stateBinder; - launcher.setOnResumeCallback(handler); - handler.onNewIntent(launcher); + public static void handleCreate(Launcher launcher, Intent intent) { + if (intent.getExtras() != null) { + IBinder stateBinder = intent.getExtras().getBinder(EXTRA_STATE_HANDLER); + if (stateBinder instanceof InternalStateHandler) { + InternalStateHandler handler = (InternalStateHandler) stateBinder; + launcher.setOnResumeCallback(handler); + handler.onCreate(launcher); + } + intent.getExtras().remove(EXTRA_STATE_HANDLER); + } + } + + public static void handleNewIntent(Launcher launcher, Intent intent, boolean alreadyOnHome) { + if (intent.getExtras() != null) { + IBinder stateBinder = intent.getExtras().getBinder(EXTRA_STATE_HANDLER); + if (stateBinder instanceof InternalStateHandler) { + InternalStateHandler handler = (InternalStateHandler) stateBinder; + launcher.setOnResumeCallback(handler); + handler.onNewIntent(launcher, alreadyOnHome); + } + intent.getExtras().remove(EXTRA_STATE_HANDLER); } - intent.getExtras().remove(EXTRA_STATE_HANDLER); } }