Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aeb298c4 authored by Winson's avatar Winson
Browse files

Fixing bad regression in alt-tab layout.

- Removing the notion of drawing the task header thumbnail on preload.
  This would not work because we could not know the stack state until
  showRecents() is actually called.  Instead, we keep a cached thumbnail
  bitmap that we draw into when we start the activity, which is only
  updated when the layout changes.
- Ensuring that with the smaller task views in the focused layout
  overlap and do not show a gap between them (this was introduced when
  the task views were made smaller to show more of the task behind it)
- Ensure that both alt-tab and paging both default to focused state
- Always reset the stack layout to clear the task overrides so that
  we don’t inadvertently get overrides when alt-tabbing

Bug: 28014191
Change-Id: Ibc93597e9c027ce5abd65a8b77c0628864814c9b
parent 3c2c34bb
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -178,7 +178,7 @@
    <!-- Recents: The relative range of visible tasks from the current scroll position
         while the stack is focused. -->
    <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
    <item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
    <item name="recents_layout_focused_range_max" format="float" type="integer">2</item>

    <!-- Recents: The relative range of visible tasks from the current scroll position
         while the stack is not focused. -->
+54 −104
Original line number Diff line number Diff line
@@ -144,7 +144,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener

    // Task launching
    Rect mTaskStackBounds = new Rect();
    Rect mLastTaskViewBounds = new Rect();
    TaskViewTransform mTmpTransform = new TaskViewTransform();
    int mStatusBarHeight;
    int mNavBarHeight;
@@ -169,8 +168,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
        }
    });

    protected Bitmap mThumbnailTransitionBitmapCache;
    Task mThumbnailTransitionBitmapCacheKey;
    protected Bitmap mThumbTransitionBitmapCache;

    public RecentsImpl(Context context) {
        mContext = context;
@@ -186,7 +184,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener

        // Initialize the static configuration resources
        reloadHeaderBarLayout();
        updateHeaderBarLayout(null /* stack */);

        // When we start, preload the data associated with the previous recent tasks.
        // We can use a new plan since the caches will be the same.
@@ -201,12 +198,11 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
    }

    public void onBootCompleted() {
        updateHeaderBarLayout(null /* stack */);
        // Do nothing
    }

    public void onConfigurationChanged() {
        reloadHeaderBarLayout();
        updateHeaderBarLayout(null /* stack */);
    }

    /**
@@ -368,9 +364,14 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
            loader.preloadTasks(sInstanceLoadPlan, topTask.id, topTaskHome.value);
            TaskStack stack = sInstanceLoadPlan.getTaskStack();
            if (stack.getTaskCount() > 0) {
                // We try and draw the thumbnail transition bitmap in parallel before
                // toggle/show recents is called
                preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView);
                // Only preload the icon (but not the thumbnail since it may not have been taken for
                // the pausing activity)
                preloadIcon(topTask);

                // At this point, we don't know anything about the stack state.  So only calculate
                // the dimensions of the thumbnail that we need for the transition into Recents, but
                // do not draw it until we construct the activity options when we start Recents
                updateHeaderBarLayout(stack);
            }
        }
    }
@@ -601,21 +602,30 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
        if (stack != null) {
            stackLayout.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
                    mTaskStackBounds);
            stackLayout.reset();
            stackLayout.initialize(windowRect, mTaskStackBounds,
                    TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
            mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
        }
        Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
        if (!taskViewBounds.equals(mLastTaskViewBounds)) {
            mLastTaskViewBounds.set(taskViewBounds);

            Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
            int taskViewWidth = taskViewBounds.width();
            synchronized (mHeaderBarLock) {
                if (mHeaderBar.getMeasuredWidth() != taskViewWidth ||
                        mHeaderBar.getMeasuredHeight() != mTaskBarHeight) {
                    mHeaderBar.measure(
                        View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
                        View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
                }
                mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
            }

            // Update the transition bitmap to match the new header bar height
            if (mThumbTransitionBitmapCache == null ||
                    (mThumbTransitionBitmapCache.getWidth() != taskViewWidth) ||
                    (mThumbTransitionBitmapCache.getHeight() != mTaskBarHeight)) {
                mThumbTransitionBitmapCache = Bitmap.createBitmap(taskViewWidth,
                        mTaskBarHeight, Bitmap.Config.ARGB_8888);
            }
        }
    }

@@ -651,40 +661,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
        Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
    }

    /**
     * Caches the header thumbnail used for a window animation asynchronously into
     * {@link #mThumbnailTransitionBitmapCache}.
     */
    private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
            TaskStack stack, TaskStackView stackView) {
        preloadIcon(topTask);

        // Update the header bar if necessary
        updateHeaderBarLayout(stack);

        // Update the destination rect
        final Task toTask = new Task();
        final TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask);
        ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() {
            @Override
            public void run() {
                final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
                if (transitionBitmap != null) {
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            mThumbnailTransitionBitmapCache = transitionBitmap;
                            mThumbnailTransitionBitmapCacheKey = toTask;
                        }
                    });
                } else {
                    Log.e(TAG, "Could not load thumbnail for task: " + toTask + " at transform: " +
                            toTransform);
                }
            }
        });
    }

    /**
     * Creates the activity options for a unknown state->recents transition.
     */
@@ -724,9 +700,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
                if (task.isFreeformTask()) {
                    mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
                                    stackScroller.getStackScroll(), mTmpTransform, null);
                    Bitmap thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform,
                            mThumbTransitionBitmapCache);
                    Rect toTaskRect = new Rect();
                    mTmpTransform.rect.round(toTaskRect);
                    Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform);
                    specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
                }
            }
@@ -738,9 +715,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
            // Update the destination rect
            Task toTask = new Task();
            TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask);
            RectF toTaskRect = toTransform.rect;
            Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform);
            Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform,
                    mThumbTransitionBitmapCache);
            if (thumbnail != null) {
                RectF toTaskRect = toTransform.rect;
                return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
                        thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
                        (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
@@ -750,22 +728,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
        }
    }

    private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask,
            TaskViewTransform toTransform) {
        Bitmap thumbnail;
        if (mThumbnailTransitionBitmapCacheKey != null
                && mThumbnailTransitionBitmapCacheKey.key != null
                && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
            thumbnail = mThumbnailTransitionBitmapCache;
            mThumbnailTransitionBitmapCacheKey = null;
            mThumbnailTransitionBitmapCache = null;
        } else {
            preloadIcon(topTask);
            thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
        }
        return thumbnail;
    }

    /**
     * Returns the transition rect for the given task id.
     */
@@ -793,26 +755,19 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
    /**
     * Draws the header of a task used for the window animation into a bitmap.
     */
    private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
    private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform,
            Bitmap thumbnail) {
        SystemServicesProxy ssp = Recents.getSystemServices();
        if (toTransform != null && toTask.key != null) {
            Bitmap thumbnail;
            synchronized (mHeaderBarLock) {
                int toHeaderWidth = (int) toTransform.rect.width();
                int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
                if (toHeaderWidth <= 0 || toHeaderHeight <= 0) {
                    return null;
                }
                boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
                mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
                        (int) toTransform.rect.height());
                thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
                        Bitmap.Config.ARGB_8888);
                if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
                    thumbnail.eraseColor(0xFFff0000);
                } else {
                    thumbnail.eraseColor(0);
                    Canvas c = new Canvas(thumbnail);
                    c.scale(toTransform.scale, toTransform.scale);
                    // Workaround for b/27815919, reset the callback so that we do not trigger an
                    // invalidate on the header bar as a result of updating the icon
                    Drawable icon = mHeaderBar.getIconView().getDrawable();
@@ -854,6 +809,18 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
        boolean hasRecentTasks = stack.getTaskCount() > 0;
        boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;

        // Update the launch state that we need in updateHeaderBarLayout()
        launchState.launchedFromHome = !useThumbnailTransition;
        launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
        launchState.launchedViaDockGesture = mLaunchedWhileDocking;
        launchState.launchedViaDragGesture = mDraggingInRecents;
        launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
        launchState.launchedWithAltTab = mTriggeredFromAltTab;

        // Preload the icon (this will be a null-op if we have preloaded the icon already in
        // preloadRecents())
        preloadIcon(topTask);

        // Update the header bar if necessary
        updateHeaderBarLayout(stack);

@@ -861,44 +828,27 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
        TaskStackLayoutAlgorithm.VisibilityReport stackVr =
                mDummyStackView.computeStackVisibilityReport();

        // Update the launch state
        launchState.launchedFromHome = false;
        launchState.launchedFromApp = mLaunchedWhileDocking;
        launchState.launchedViaDockGesture = mLaunchedWhileDocking;
        launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
        launchState.launchedWithAltTab = mTriggeredFromAltTab;
        // Update the remaining launch state
        launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
        launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
        launchState.launchedViaDragGesture = mDraggingInRecents;

        if (!animate) {
            startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1));
            return;
        }

        ActivityOptions opts;
        if (useThumbnailTransition) {
            launchState.launchedFromApp = true;

            // Try starting with a thumbnail transition
            ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, mDummyStackView);
            if (opts != null) {
                startRecentsActivity(opts);
            opts = getThumbnailTransitionActivityOptions(topTask, mDummyStackView);
        } else {
                // Fall through below to the non-thumbnail transition
                useThumbnailTransition = false;
            }
        }

        if (!useThumbnailTransition) {
            launchState.launchedFromHome = true;

            // If there is no thumbnail transition, but is launching from home into recents, then
            // use a quick home transition
            ActivityOptions opts = hasRecentTasks
            opts = hasRecentTasks
                ? getHomeTransitionActivityOptions()
                : getUnknownTransitionActivityOptions();
            startRecentsActivity(opts);
        }
        startRecentsActivity(opts);
        mLastToggleTime = SystemClock.elapsedRealtime();
    }

+1 −1
Original line number Diff line number Diff line
@@ -124,7 +124,7 @@ public class RecentsTvImpl extends RecentsImpl{
     */
    private ActivityOptions getThumbnailTransitionActivityOptionsForTV(
            ActivityManager.RunningTaskInfo topTask) {
        Bitmap thumbnail = mThumbnailTransitionBitmapCache;
        Bitmap thumbnail = mThumbTransitionBitmapCache;
        Rect rect = TaskCardView.getStartingCardThumbnailRect(mContext);
        if (thumbnail != null) {
            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
+2 −1
Original line number Diff line number Diff line
@@ -456,7 +456,8 @@ public class TaskStackAnimationHelper {
        TaskStack stack = mStackView.getStack();

        final float curScroll = stackScroller.getStackScroll();
        final float newScroll = stackLayout.getStackScrollForTask(newFocusedTask);
        final float newScroll = stackScroller.getBoundedStackScroll(
                stackLayout.getStackScrollForTask(newFocusedTask));
        boolean willScrollToFront = newScroll > curScroll;
        boolean willScroll = Float.compare(newScroll, curScroll) != 0;

+28 −4
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.view.ViewDebug;

import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.RecentsDebugFlags;
@@ -530,8 +531,12 @@ public class TaskStackLayoutAlgorithm {
                ? stack.indexOfStackTask(launchTask)
                : mNumStackTasks - 1;
        if (getInitialFocusState() == STATE_FOCUSED) {
            int maxBottomOffset = mStackBottomOffset + mTaskRect.height();
            float maxBottomNormX = getNormalizedXFromFocusedY(maxBottomOffset, FROM_BOTTOM);
            mFocusedRange.offset(0f);
            mMinScrollP = 0;
            mMaxScrollP = Math.max(mMinScrollP, mNumStackTasks - 1);
            mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) -
                    Math.max(0, mFocusedRange.getAbsoluteX(maxBottomNormX)));
            if (launchState.launchedFromHome) {
                mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
            } else {
@@ -555,7 +560,10 @@ public class TaskStackLayoutAlgorithm {
                    Math.max(0, mUnfocusedRange.getAbsoluteX(maxBottomNormX)));
            boolean scrollToFront = launchState.launchedFromHome ||
                    launchState.launchedViaDockGesture;
            if (scrollToFront) {
            if (launchState.launchedWithAltTab) {
                mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
                mInitialNormX = null;
            } else if (scrollToFront) {
                mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
                mInitialNormX = null;
            } else {
@@ -652,8 +660,9 @@ public class TaskStackLayoutAlgorithm {
     * Returns the default focus state.
     */
    public int getInitialFocusState() {
        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
        RecentsDebugFlags debugFlags = Recents.getDebugFlags();
        if (debugFlags.isPagingEnabled()) {
        if (debugFlags.isPagingEnabled() || launchState.launchedWithAltTab) {
            return STATE_FOCUSED;
        } else {
            return STATE_UNFOCUSED;
@@ -1026,6 +1035,18 @@ public class TaskStackLayoutAlgorithm {
        return mUnfocusedCurveInterpolator.getX(offsetPct);
    }

    /**
     * Returns the normalized x on the focused curve given an absolute Y position (relative to the
     * stack height).
     */
    private float getNormalizedXFromFocusedY(float y, @AnchorSide int fromSide) {
        float offset = (fromSide == FROM_TOP)
                ? mStackRect.height() - y
                : y;
        float offsetPct = offset / mStackRect.height();
        return mFocusedCurveInterpolator.getX(offsetPct);
    }

    /**
     * Creates a new path for the focused curve.
     */
@@ -1036,10 +1057,13 @@ public class TaskStackLayoutAlgorithm {
        float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
        float bottomPeekHeightPct = (float) (mStackBottomOffset + mFocusedBottomPeekHeight) /
                mStackRect.height();
        float minBottomPeekHeightPct = (float) (mFocusedTopPeekHeight + mTaskRect.height() -
                mMinMargin) / mStackRect.height();
        Path p = new Path();
        p.moveTo(0f, 1f);
        p.lineTo(0.5f, 1f - topPeekHeightPct);
        p.lineTo(1f - (0.5f / mFocusedRange.relativeMax), bottomPeekHeightPct);
        p.lineTo(1f - (0.5f / mFocusedRange.relativeMax), Math.max(1f - minBottomPeekHeightPct,
                bottomPeekHeightPct));
        p.lineTo(1f, 0f);
        return p;
    }