diff options
Diffstat (limited to 'libs')
31 files changed, 594 insertions, 186 deletions
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml index dcce4698c252..ab64f9e359b0 100644 --- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml +++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml @@ -67,7 +67,7 @@ <!-- Temporarily extending the background to show an edu text hint for opening the menu --> <FrameLayout - android:id="@+id/tv_pip_menu_edu_text_drawer_placeholder" + android:id="@+id/tv_pip_menu_edu_text_container" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/tv_pip" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 47d3a5c52074..dc27ceb7f51c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -20,6 +20,9 @@ import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTas import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityTaskManager; @@ -37,7 +40,9 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings.Global; +import android.util.DisplayMetrics; import android.util.Log; +import android.util.MathUtils; import android.util.SparseArray; import android.view.IRemoteAnimationRunner; import android.view.InputDevice; @@ -56,6 +61,7 @@ import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.view.AppearanceRegion; +import com.android.wm.shell.animation.FlingAnimationUtils; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; @@ -80,6 +86,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont public static boolean IS_U_ANIMATION_ENABLED = SystemProperties.getInt("persist.wm.debug.predictive_back_anim", SETTING_VALUE_ON) == SETTING_VALUE_ON; + + public static final float FLING_MAX_LENGTH_SECONDS = 0.1f; // 100ms + public static final float FLING_SPEED_UP_FACTOR = 0.6f; + + /** + * The maximum additional progress in case of fling gesture. + * The end animation starts after the user lifts the finger from the screen, we continue to + * fire {@link BackEvent}s until the velocity reaches 0. + */ + private static final float MAX_FLING_PROGRESS = 0.3f; /* 30% of the screen */ + /** Predictive back animation developer option */ private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false); /** @@ -96,6 +113,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private boolean mShouldStartOnNextMoveEvent = false; /** @see #setTriggerBack(boolean) */ private boolean mTriggerBack; + private FlingAnimationUtils mFlingAnimationUtils; @Nullable private BackNavigationInfo mBackNavigationInfo; @@ -174,6 +192,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mBgHandler = bgHandler; shellInit.addInitCallback(this::onInit, this); mAnimationBackground = backAnimationBackground; + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + mFlingAnimationUtils = new FlingAnimationUtils.Builder(displayMetrics) + .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS) + .setSpeedUpFactor(FLING_SPEED_UP_FACTOR) + .build(); } @VisibleForTesting @@ -465,6 +488,78 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } } + + /** + * Allows us to manage the fling gesture, it smoothly animates the current progress value to + * the final position, calculated based on the current velocity. + * + * @param callback the callback to be invoked when the animation ends. + */ + private void dispatchOrAnimateOnBackInvoked(IOnBackInvokedCallback callback) { + if (callback == null) { + return; + } + + boolean animationStarted = false; + + if (mBackNavigationInfo != null && mBackNavigationInfo.isAnimationCallback()) { + + final BackMotionEvent backMotionEvent = mTouchTracker.createProgressEvent(); + if (backMotionEvent != null) { + // Constraints - absolute values + float minVelocity = mFlingAnimationUtils.getMinVelocityPxPerSecond(); + float maxVelocity = mFlingAnimationUtils.getHighVelocityPxPerSecond(); + float maxX = mTouchTracker.getMaxX(); // px + float maxFlingDistance = maxX * MAX_FLING_PROGRESS; // px + + // Current state + float currentX = backMotionEvent.getTouchX(); + float velocity = MathUtils.constrain(backMotionEvent.getVelocityX(), + -maxVelocity, maxVelocity); + + // Target state + float animationFaction = velocity / maxVelocity; // value between -1 and 1 + float flingDistance = animationFaction * maxFlingDistance; // px + float endX = MathUtils.constrain(currentX + flingDistance, 0f, maxX); + + if (!Float.isNaN(endX) + && currentX != endX + && Math.abs(velocity) >= minVelocity) { + ValueAnimator animator = ValueAnimator.ofFloat(currentX, endX); + + mFlingAnimationUtils.apply( + /* animator = */ animator, + /* currValue = */ currentX, + /* endValue = */ endX, + /* velocity = */ velocity, + /* maxDistance = */ maxFlingDistance + ); + + animator.addUpdateListener(animation -> { + Float animatedValue = (Float) animation.getAnimatedValue(); + float progress = mTouchTracker.getProgress(animatedValue); + final BackMotionEvent backEvent = mTouchTracker + .createProgressEvent(progress); + dispatchOnBackProgressed(mActiveCallback, backEvent); + }); + + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + dispatchOnBackInvoked(callback); + } + }); + animator.start(); + animationStarted = true; + } + } + } + + if (!animationStarted) { + dispatchOnBackInvoked(callback); + } + } + private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) { if (callback == null) { return; @@ -530,7 +625,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (mBackNavigationInfo != null) { final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback(); if (mTriggerBack) { - dispatchOnBackInvoked(callback); + dispatchOrAnimateOnBackInvoked(callback); } else { dispatchOnBackCancelled(callback); } @@ -605,7 +700,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // The next callback should be {@link #onBackAnimationFinished}. if (mTriggerBack) { - dispatchOnBackInvoked(mActiveCallback); + dispatchOrAnimateOnBackInvoked(mActiveCallback); } else { dispatchOnBackCancelled(mActiveCallback); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java index 904574b08562..7a00f5b9bab4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java @@ -16,7 +16,10 @@ package com.android.wm.shell.back; +import android.annotation.FloatRange; import android.os.SystemProperties; +import android.util.MathUtils; +import android.view.MotionEvent; import android.view.RemoteAnimationTarget; import android.window.BackEvent; import android.window.BackMotionEvent; @@ -99,28 +102,42 @@ class TouchTracker { } BackMotionEvent createProgressEvent() { - float progressThreshold = PROGRESS_THRESHOLD >= 0 - ? PROGRESS_THRESHOLD : mProgressThreshold; - progressThreshold = progressThreshold == 0 ? 1 : progressThreshold; float progress = 0; // Progress is always 0 when back is cancelled and not restarted. if (!mCancelled) { - // If back is committed, progress is the distance between the last and first touch - // point, divided by the max drag distance. Otherwise, it's the distance between - // the last touch point and the starting threshold, divided by max drag distance. - // The starting threshold is initially the first touch location, and updated to - // the location everytime back is restarted after being cancelled. - float startX = mTriggerBack ? mInitTouchX : mStartThresholdX; - float deltaX = Math.max( - mSwipeEdge == BackEvent.EDGE_LEFT - ? mLatestTouchX - startX - : startX - mLatestTouchX, - 0); - progress = Math.min(Math.max(deltaX / progressThreshold, 0), 1); + progress = getProgress(mLatestTouchX); } return createProgressEvent(progress); } + /** + * Progress value computed from the touch position. + * + * @param touchX the X touch position of the {@link MotionEvent}. + * @return progress value + */ + @FloatRange(from = 0.0, to = 1.0) + float getProgress(float touchX) { + // If back is committed, progress is the distance between the last and first touch + // point, divided by the max drag distance. Otherwise, it's the distance between + // the last touch point and the starting threshold, divided by max drag distance. + // The starting threshold is initially the first touch location, and updated to + // the location everytime back is restarted after being cancelled. + float startX = mTriggerBack ? mInitTouchX : mStartThresholdX; + float deltaX = Math.abs(startX - touchX); + float maxX = getMaxX(); + maxX = maxX == 0 ? 1 : maxX; + return MathUtils.constrain(deltaX / maxX, 0, 1); + } + + /** + * Maximum X value (in pixels). + * Progress is considered to be completed (1f) when this limit is exceeded. + */ + float getMaxX() { + return PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold; + } + BackMotionEvent createProgressEvent(float progress) { return new BackMotionEvent( /* touchX = */ mLatestTouchX, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 79e0a4868cae..d3f395846894 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -64,7 +64,11 @@ import java.util.concurrent.Executor; public class Bubble implements BubbleViewProvider { private static final String TAG = "Bubble"; - public static final String KEY_APP_BUBBLE = "key_app_bubble"; + /** A string suffix used in app bubbles' {@link #mKey}. */ + private static final String KEY_APP_BUBBLE = "key_app_bubble"; + + /** Whether the bubble is an app bubble. */ + private final boolean mIsAppBubble; private final String mKey; @Nullable @@ -181,7 +185,7 @@ public class Bubble implements BubbleViewProvider { private PendingIntent mDeleteIntent; /** - * Used only for a special bubble in the stack that has the key {@link #KEY_APP_BUBBLE}. + * Used only for a special bubble in the stack that has {@link #mIsAppBubble} set to true. * There can only be one of these bubbles in the stack and this intent will be populated for * that bubble. */ @@ -216,24 +220,54 @@ public class Bubble implements BubbleViewProvider { mMainExecutor = mainExecutor; mTaskId = taskId; mBubbleMetadataFlagListener = listener; + mIsAppBubble = false; } - public Bubble(Intent intent, + private Bubble( + Intent intent, UserHandle user, @Nullable Icon icon, + boolean isAppBubble, + String key, Executor mainExecutor) { - mKey = KEY_APP_BUBBLE; mGroupKey = null; mLocusId = null; mFlags = 0; mUser = user; mIcon = icon; + mIsAppBubble = isAppBubble; + mKey = key; mShowBubbleUpdateDot = false; mMainExecutor = mainExecutor; mTaskId = INVALID_TASK_ID; mAppIntent = intent; mDesiredHeight = Integer.MAX_VALUE; mPackageName = intent.getPackage(); + + } + + /** Creates an app bubble. */ + public static Bubble createAppBubble( + Intent intent, + UserHandle user, + @Nullable Icon icon, + Executor mainExecutor) { + return new Bubble(intent, + user, + icon, + /* isAppBubble= */ true, + /* key= */ getAppBubbleKeyForApp(intent.getPackage(), user), + mainExecutor); + } + + /** + * Returns the key for an app bubble from an app with package name, {@code packageName} on an + * Android user, {@code user}. + */ + public static String getAppBubbleKeyForApp(String packageName, UserHandle user) { + Objects.requireNonNull(packageName); + Objects.requireNonNull(user); + return KEY_APP_BUBBLE + ":" + user.getIdentifier() + ":" + packageName; } @VisibleForTesting(visibility = PRIVATE) @@ -241,6 +275,7 @@ public class Bubble implements BubbleViewProvider { final Bubbles.BubbleMetadataFlagListener listener, final Bubbles.PendingIntentCanceledListener intentCancelListener, Executor mainExecutor) { + mIsAppBubble = false; mKey = entry.getKey(); mGroupKey = entry.getGroupKey(); mLocusId = entry.getLocusId(); @@ -815,7 +850,7 @@ public class Bubble implements BubbleViewProvider { } boolean isAppBubble() { - return KEY_APP_BUBBLE.equals(mKey); + return mIsAppBubble; } Intent getSettingsIntent(final Context context) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index c407b0624985..21f02b10035b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -24,7 +24,6 @@ import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; @@ -1193,14 +1192,15 @@ public class BubbleController implements ConfigurationChangeListener, return; } + String appBubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user); PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier()); - if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return; + if (!isResizableActivity(intent, packageManager, appBubbleKey)) return; - Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE); + Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(appBubbleKey); if (existingAppBubble != null) { BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble(); if (isStackExpanded()) { - if (selectedBubble != null && KEY_APP_BUBBLE.equals(selectedBubble.getKey())) { + if (selectedBubble != null && appBubbleKey.equals(selectedBubble.getKey())) { // App bubble is expanded, lets collapse collapseStack(); } else { @@ -1214,7 +1214,7 @@ public class BubbleController implements ConfigurationChangeListener, } } else { // App bubble does not exist, lets add and expand it - Bubble b = new Bubble(intent, user, icon, mMainExecutor); + Bubble b = Bubble.createAppBubble(intent, user, icon, mMainExecutor); b.setShouldAutoExpand(true); inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false); } @@ -1247,8 +1247,8 @@ public class BubbleController implements ConfigurationChangeListener, } /** Sets the app bubble's taskId which is cached for SysUI. */ - public void setAppBubbleTaskId(int taskId) { - mImpl.mCachedState.setAppBubbleTaskId(taskId); + public void setAppBubbleTaskId(String key, int taskId) { + mImpl.mCachedState.setAppBubbleTaskId(key, taskId); } /** @@ -2045,7 +2045,8 @@ public class BubbleController implements ConfigurationChangeListener, private HashSet<String> mSuppressedBubbleKeys = new HashSet<>(); private HashMap<String, String> mSuppressedGroupToNotifKeys = new HashMap<>(); private HashMap<String, Bubble> mShortcutIdToBubble = new HashMap<>(); - private int mAppBubbleTaskId = INVALID_TASK_ID; + + private HashMap<String, Integer> mAppBubbleTaskIds = new HashMap(); private ArrayList<Bubble> mTmpBubbles = new ArrayList<>(); @@ -2077,20 +2078,20 @@ public class BubbleController implements ConfigurationChangeListener, mSuppressedBubbleKeys.clear(); mShortcutIdToBubble.clear(); - mAppBubbleTaskId = INVALID_TASK_ID; + mAppBubbleTaskIds.clear(); for (Bubble b : mTmpBubbles) { mShortcutIdToBubble.put(b.getShortcutId(), b); updateBubbleSuppressedState(b); - if (KEY_APP_BUBBLE.equals(b.getKey())) { - mAppBubbleTaskId = b.getTaskId(); + if (b.isAppBubble()) { + mAppBubbleTaskIds.put(b.getKey(), b.getTaskId()); } } } /** Sets the app bubble's taskId which is cached for SysUI. */ - synchronized void setAppBubbleTaskId(int taskId) { - mAppBubbleTaskId = taskId; + synchronized void setAppBubbleTaskId(String key, int taskId) { + mAppBubbleTaskIds.put(key, taskId); } /** @@ -2143,7 +2144,7 @@ public class BubbleController implements ConfigurationChangeListener, pw.println(" suppressing: " + key); } - pw.print("mAppBubbleTaskId: " + mAppBubbleTaskId); + pw.print("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values()); } } @@ -2205,7 +2206,7 @@ public class BubbleController implements ConfigurationChangeListener, @Override public boolean isAppBubbleTaskId(int taskId) { - return mCachedState.mAppBubbleTaskId == taskId; + return mCachedState.mAppBubbleTaskIds.values().contains(taskId); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 92b969bb6f97..cc8f50e09fcb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -17,7 +17,6 @@ package com.android.wm.shell.bubbles; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; -import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; @@ -780,7 +779,7 @@ public class BubbleData { || !(reason == Bubbles.DISMISS_AGED || reason == Bubbles.DISMISS_USER_GESTURE || reason == Bubbles.DISMISS_RELOAD_FROM_DISK) - || KEY_APP_BUBBLE.equals(bubble.getKey())) { + || bubble.isAppBubble()) { return; } if (DEBUG_BUBBLE_DATA) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 684a23a198c2..6c482c831152 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -287,9 +287,9 @@ public class BubbleExpandedView extends LinearLayout { // The taskId is saved to use for removeTask, preventing appearance in recent tasks. mTaskId = taskId; - if (Bubble.KEY_APP_BUBBLE.equals(getBubbleKey())) { + if (mBubble != null && mBubble.isAppBubble()) { // Let the controller know sooner what the taskId is. - mController.setAppBubbleTaskId(mTaskId); + mController.setAppBubbleTaskId(mBubble.getKey(), mTaskId); } // With the task org, the taskAppeared callback will only happen once the task has diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index 4970fa0cb087..56616cb6bd88 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -165,6 +165,10 @@ public class SplitDecorManager extends WindowlessWindowManager { t.remove(mGapBackgroundLeash); mGapBackgroundLeash = null; } + if (mScreenshot != null) { + t.remove(mScreenshot); + mScreenshot = null; + } mHostLeash = null; mIcon = null; mResizingIconView = null; @@ -324,6 +328,8 @@ public class SplitDecorManager extends WindowlessWindowManager { if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) { if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) { mScreenshotAnimator.cancel(); + } else if (mScreenshot != null) { + t.remove(mScreenshot); } mTempRect.set(mOldBounds); @@ -340,6 +346,8 @@ public class SplitDecorManager extends WindowlessWindowManager { if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) { if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) { mScreenshotAnimator.cancel(); + } else if (mScreenshot != null) { + t.remove(mScreenshot); } mScreenshot = screenshot; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index d04ce1540980..b6216b340b38 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -146,13 +146,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, t.apply(); // execute the runnable if non-null after WCT is applied to finish resizing pip - if (mPipFinishResizeWCTRunnable != null) { - mPipFinishResizeWCTRunnable.run(); - mPipFinishResizeWCTRunnable = null; - } + maybePerformFinishResizeCallback(); } }; + private void maybePerformFinishResizeCallback() { + if (mPipFinishResizeWCTRunnable != null) { + mPipFinishResizeWCTRunnable.run(); + mPipFinishResizeWCTRunnable = null; + } + } + // These callbacks are called on the update thread private final PipAnimationController.PipAnimationCallback mPipAnimationCallback = new PipAnimationController.PipAnimationCallback() { @@ -619,11 +623,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, * Removes PiP immediately. */ public void removePip() { - if (!mPipTransitionState.isInPip() || mToken == null) { + if (!mPipTransitionState.isInPip() || mToken == null || mLeash == null) { ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Not allowed to removePip in current state" - + " mState=%d mToken=%s", TAG, mPipTransitionState.getTransitionState(), - mToken); + + " mState=%d mToken=%s mLeash=%s", TAG, + mPipTransitionState.getTransitionState(), mToken, mLeash); return; } @@ -1007,6 +1011,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mPipTransitionController.onFixedRotationFinished(); clearWaitForFixedRotation(); return; } @@ -1527,6 +1532,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (snapshotSurface != null) { mSyncTransactionQueue.queue(wct); mSyncTransactionQueue.runInSync(t -> { + // reset the pinch gesture + maybePerformFinishResizeCallback(); + // Scale the snapshot from its pre-resize bounds to the post-resize bounds. mSurfaceTransactionHelper.scale(t, snapshotSurface, preResizeBounds, snapshotDest); @@ -1606,6 +1614,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { mSplitScreenOptional.ifPresent(splitScreenController -> splitScreenController.enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct)); + } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) { + // when leaving PiP we can call the callback without sync + maybePerformFinishResizeCallback(); + mTaskOrganizer.applyTransaction(wct); } else { mTaskOrganizer.applySyncTransaction(wct, mPipFinishResizeWCTCallback); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 5755a10897af..99cb6f782d01 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -409,9 +409,31 @@ public class PipTransition extends PipTransitionController { @Override public void onFixedRotationStarted() { + fadeEnteredPipIfNeed(false /* show */); + } + + @Override + public void onFixedRotationFinished() { + fadeEnteredPipIfNeed(true /* show */); + } + + private void fadeEnteredPipIfNeed(boolean show) { // The transition with this fixed rotation may be handled by other handler before reaching // PipTransition, so we cannot do this in #startAnimation. - if (mPipTransitionState.getTransitionState() == ENTERED_PIP && !mHasFadeOut) { + if (!mPipTransitionState.hasEnteredPip()) { + return; + } + if (show && mHasFadeOut) { + // If there is a pending transition, then let startAnimation handle it. And if it is + // handled, mHasFadeOut will be set to false and this runnable will be no-op. Otherwise + // make sure the PiP will reshow, e.g. swipe-up with fixed rotation (fade-out) but + // return to the current app (only finish the recent transition). + mTransitions.runOnIdle(() -> { + if (mHasFadeOut && mPipTransitionState.hasEnteredPip()) { + fadeExistingPip(true /* show */); + } + }); + } else if (!show && !mHasFadeOut) { // Fade out the existing PiP to avoid jump cut during seamless rotation. fadeExistingPip(false /* show */); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index ff7ab8ba1d97..949d6f558c32 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -123,6 +123,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH public void onFixedRotationStarted() { } + /** Called when the fixed rotation finished. */ + public void onFixedRotationFinished() { + } + public PipTransitionController( @NonNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java index 222307fba8c2..5f6b3fe1e250 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java @@ -31,6 +31,12 @@ import java.util.Objects; abstract class TvPipAction { + /** + * Extras key for adding a boolean to the {@link Notification.Action} to differentiate custom + * from system actions, most importantly to identify custom close actions. + **/ + public static final String EXTRA_IS_PIP_CUSTOM_ACTION = "EXTRA_IS_PIP_CUSTOM_ACTION"; + @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"ACTION_"}, value = { ACTION_FULLSCREEN, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java index bca27a5c6636..977aad4a898a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java @@ -86,7 +86,7 @@ public class TvPipCustomAction extends TvPipAction { Bundle extras = new Bundle(); extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION, mRemoteAction.getContentDescription()); - extras.putBoolean(Notification.EXTRA_CONTAINS_CUSTOM_VIEW, true); + extras.putBoolean(TvPipAction.EXTRA_IS_PIP_CUSTOM_ACTION, true); builder.addExtras(extras); builder.setSemanticAction(isCloseAction() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java index 6eef22562caa..f86f987039ba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java @@ -23,6 +23,7 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE; +import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.drawable.Drawable; @@ -115,6 +116,10 @@ class TvPipMenuEduTextDrawer extends FrameLayout { scheduleLifecycleEvents(); } + int getEduTextDrawerHeight() { + return getVisibility() == GONE ? 0 : getHeight(); + } + private void scheduleLifecycleEvents() { final int startScrollDelay = mContext.getResources().getInteger( R.integer.pip_edu_text_start_scroll_delay); @@ -226,20 +231,41 @@ class TvPipMenuEduTextDrawer extends FrameLayout { .start(); // Start animation to close the drawer by animating its height to 0 - final ValueAnimator heightAnimation = ValueAnimator.ofInt(getHeight(), 0); - heightAnimation.setDuration(eduTextSlideExitAnimationDuration); - heightAnimation.setInterpolator(TvPipInterpolators.BROWSE); - heightAnimation.addUpdateListener(animator -> { + final ValueAnimator heightAnimator = ValueAnimator.ofInt(getHeight(), 0); + heightAnimator.setDuration(eduTextSlideExitAnimationDuration); + heightAnimator.setInterpolator(TvPipInterpolators.BROWSE); + heightAnimator.addUpdateListener(animator -> { final ViewGroup.LayoutParams params = getLayoutParams(); params.height = (int) animator.getAnimatedValue(); setLayoutParams(params); - if (params.height == 0) { - setVisibility(GONE); + }); + heightAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(@NonNull Animator animator) { + } + + @Override + public void onAnimationEnd(@NonNull Animator animator) { + onCloseEduTextAnimationEnd(); + } + + @Override + public void onAnimationCancel(@NonNull Animator animator) { + onCloseEduTextAnimationEnd(); + } + + @Override + public void onAnimationRepeat(@NonNull Animator animator) { } }); - heightAnimation.start(); + heightAnimator.start(); + + mListener.onCloseEduTextAnimationStart(); + } - mListener.onCloseEduText(); + public void onCloseEduTextAnimationEnd() { + setVisibility(GONE); + mListener.onCloseEduTextAnimationEnd(); } /** @@ -270,11 +296,8 @@ class TvPipMenuEduTextDrawer extends FrameLayout { * A listener for edu text drawer event states. */ interface Listener { - /** - * The edu text closing impacts the size of the Picture-in-Picture window and influences - * how it is positioned on the screen. - */ - void onCloseEduText(); + void onCloseEduTextAnimationStart(); + void onCloseEduTextAnimationEnd(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java index 6eb719ba60a3..d07641892552 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java @@ -57,7 +57,8 @@ import java.util.List; * A View that represents Pip Menu on TV. It's responsible for displaying the Pip menu actions from * the TvPipActionsProvider as well as the buttons for manually moving the PiP. */ -public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.Listener { +public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.Listener, + TvPipMenuEduTextDrawer.Listener { private static final String TAG = "TvPipMenuView"; private final TvPipMenuView.Listener mListener; @@ -76,6 +77,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L private final View mDimLayer; private final TvPipMenuEduTextDrawer mEduTextDrawer; + private final ViewGroup mEduTextContainer; private final int mPipMenuOuterSpace; private final int mPipMenuBorderWidth; @@ -139,9 +141,9 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L mPipMenuBorderWidth = context.getResources() .getDimensionPixelSize(R.dimen.pip_menu_border_width); - mEduTextDrawer = new TvPipMenuEduTextDrawer(mContext, mainHandler, mListener); - ((FrameLayout) findViewById(R.id.tv_pip_menu_edu_text_drawer_placeholder)) - .addView(mEduTextDrawer); + mEduTextDrawer = new TvPipMenuEduTextDrawer(mContext, mainHandler, this); + mEduTextContainer = (ViewGroup) findViewById(R.id.tv_pip_menu_edu_text_container); + mEduTextContainer.addView(mEduTextDrawer); } void onPipTransitionToTargetBoundsStarted(Rect targetBounds) { @@ -235,11 +237,13 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L * pip menu when it gains focus. */ private void updatePipFrameBounds() { - final ViewGroup.LayoutParams pipFrameParams = mPipFrameView.getLayoutParams(); - if (pipFrameParams != null) { - pipFrameParams.width = mCurrentPipBounds.width() + 2 * mPipMenuBorderWidth; - pipFrameParams.height = mCurrentPipBounds.height() + 2 * mPipMenuBorderWidth; - mPipFrameView.setLayoutParams(pipFrameParams); + if (mPipFrameView.getVisibility() == VISIBLE) { + final ViewGroup.LayoutParams pipFrameParams = mPipFrameView.getLayoutParams(); + if (pipFrameParams != null) { + pipFrameParams.width = mCurrentPipBounds.width() + 2 * mPipMenuBorderWidth; + pipFrameParams.height = mCurrentPipBounds.height() + 2 * mPipMenuBorderWidth; + mPipFrameView.setLayoutParams(pipFrameParams); + } } final ViewGroup.LayoutParams pipViewParams = mPipView.getLayoutParams(); @@ -262,7 +266,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L Rect getPipMenuContainerBounds(Rect pipBounds) { final Rect menuUiBounds = new Rect(pipBounds); menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace); - menuUiBounds.bottom += mEduTextDrawer.getHeight(); + menuUiBounds.bottom += mEduTextDrawer.getEduTextDrawerHeight(); return menuUiBounds; } @@ -406,6 +410,17 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L } @Override + public void onCloseEduTextAnimationStart() { + mListener.onCloseEduText(); + } + + @Override + public void onCloseEduTextAnimationEnd() { + mPipFrameView.setVisibility(GONE); + mEduTextContainer.setVisibility(GONE); + } + + @Override public boolean dispatchKeyEvent(KeyEvent event) { if (event.getAction() == ACTION_UP) { @@ -551,7 +566,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L } } - interface Listener extends TvPipMenuEduTextDrawer.Listener { + interface Listener { void onBackPress(); @@ -573,5 +588,11 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L * has lost focus. */ void onPipWindowFocusChanged(boolean focused); + + /** + * The edu text closing impacts the size of the Picture-in-Picture window and influences + * how it is positioned on the screen. + */ + void onCloseEduText(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index c9b3a1af6507..c4a0e9cf5a74 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -32,6 +32,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM_SHELL), WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM_SHELL), + WM_SHELL_RECENTS_TRANSITION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, + Consts.TAG_WM_SHELL), WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM_SHELL), WM_SHELL_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 44d7e6de8ec4..b55487258220 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -47,7 +47,9 @@ import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.util.TransitionUtil; @@ -96,6 +98,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, IApplicationThread appThread, IRecentsAnimationRunner listener) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsTransitionHandler.startRecentsTransition"); // only care about latest one. mAnimApp = appThread; WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -116,7 +120,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { mixer.setRecentsTransition(transition); } if (transition == null) { - controller.cancel(); + controller.cancel("startRecentsTransition"); return; } controller.setTransition(transition); @@ -127,6 +131,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { public WindowContainerTransaction handleRequest(IBinder transition, TransitionRequestInfo request) { // do not directly handle requests. Only entry point should be via startRecentsTransition + Slog.e(TAG, "RecentsTransitionHandler.handleRequest: Unexpected transition request"); return null; } @@ -143,11 +148,17 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { SurfaceControl.Transaction finishTransaction, Transitions.TransitionFinishCallback finishCallback) { final int controllerIdx = findController(transition); - if (controllerIdx < 0) return false; + if (controllerIdx < 0) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsTransitionHandler.startAnimation: no controller found"); + return false; + } final RecentsController controller = mControllers.get(controllerIdx); Transitions.setRunningRemoteTransitionDelegate(mAnimApp); mAnimApp = null; if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsTransitionHandler.startAnimation: failed to start animation"); return false; } return true; @@ -168,7 +179,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { SurfaceControl.Transaction finishTransaction) { final int idx = findController(transition); if (idx < 0) return; - mControllers.get(idx).cancel(); + mControllers.get(idx).cancel("onTransitionConsumed"); } /** There is only one of these and it gets reset on finish. */ @@ -213,39 +224,45 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { RecentsController(IRecentsAnimationRunner listener) { mListener = listener; - mDeathHandler = () -> mExecutor.execute(() -> { - if (mListener == null) return; - if (mFinishCB != null) { - finish(mWillFinishToHome, false /* leaveHint */); - } - }); + mDeathHandler = () -> { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.DeathRecipient: binder died"); + finish(mWillFinishToHome, false /* leaveHint */); + }; try { mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */); } catch (RemoteException e) { + Slog.e(TAG, "RecentsController: failed to link to death", e); mListener = null; } } void setTransition(IBinder transition) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.setTransition: id=%s", transition); mTransition = transition; } - void cancel() { + void cancel(String reason) { // restoring (to-home = false) involves submitting more WM changes, so by default, use // toHome = true when canceling. - cancel(true /* toHome */); + cancel(true /* toHome */, reason); } - void cancel(boolean toHome) { + void cancel(boolean toHome, String reason) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.cancel: toHome=%b reason=%s", toHome, reason); if (mListener != null) { try { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.cancel: calling onAnimationCanceled"); mListener.onAnimationCanceled(null, null); } catch (RemoteException e) { Slog.e(TAG, "Error canceling recents animation", e); } } if (mFinishCB != null) { - finish(toHome, false /* userLeave */); + finishInner(toHome, false /* userLeave */); } else { cleanUp(); } @@ -272,6 +289,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } } try { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.cancel: calling onAnimationCanceled with snapshots"); mListener.onAnimationCanceled(taskIds, snapshots); } catch (RemoteException e) { Slog.e(TAG, "Error canceling recents animation", e); @@ -281,6 +300,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } void cleanUp() { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsController.cleanup"); if (mListener != null && mDeathHandler != null) { mListener.asBinder().unlinkToDeath(mDeathHandler, 0 /* flags */); mDeathHandler = null; @@ -304,6 +324,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { boolean start(TransitionInfo info, SurfaceControl.Transaction t, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsController.start"); if (mListener == null || mTransition == null) { cleanUp(); return false; @@ -363,6 +384,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { info.getChanges().size() - i, info, t, mLeashMap); apps.add(target); if (TransitionUtil.isClosingType(change.getMode())) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " adding pausing taskId=%d", taskInfo.taskId); // raise closing (pausing) task to "above" layer so it isn't covered t.setLayer(target.leash, info.getChanges().size() * 3 - i); mPausingTasks.add(new TaskState(change, target.leash)); @@ -377,19 +400,23 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { // do nothing } else if (TransitionUtil.isOpeningType(change.getMode())) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " adding opening taskId=%d", taskInfo.taskId); mOpeningTasks.add(new TaskState(change, target.leash)); } } } t.apply(); try { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.start: calling onAnimationStart"); mListener.onAnimationStart(this, apps.toArray(new RemoteAnimationTarget[apps.size()]), wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]), new Rect(0, 0, 0, 0), new Rect()); } catch (RemoteException e) { Slog.e(TAG, "Error starting recents animation", e); - cancel(); + cancel("onAnimationStart() failed"); } return true; } @@ -398,14 +425,19 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { void merge(TransitionInfo info, SurfaceControl.Transaction t, Transitions.TransitionFinishCallback finishCallback) { if (mFinishCB == null) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.merge: skip, no finish callback"); // This was no-op'd (likely a repeated start) and we've already sent finish. return; } if (info.getType() == TRANSIT_SLEEP) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.merge: transit_sleep"); // A sleep event means we need to stop animations immediately, so cancel here. - cancel(); + cancel("transit_sleep"); return; } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsController.merge"); ArrayList<TransitionInfo.Change> openingTasks = null; ArrayList<TransitionInfo.Change> closingTasks = null; mOpeningSeparateHome = false; @@ -422,7 +454,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { // Tasks that are always on top (e.g. bubbles), will handle their own transition // as they are on top of everything else. So cancel the merge here. - cancel(); + cancel("task #" + taskInfo.taskId + " is always_on_top"); return; } hasTaskChange = hasTaskChange || taskInfo != null; @@ -453,7 +485,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { // Finish recents animation if the display is changed, so the default // transition handler can play the animation such as rotation effect. if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) { - cancel(mWillFinishToHome); + cancel(mWillFinishToHome, "display change"); return; } // Don't consider order-only changes as changing apps. @@ -497,7 +529,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { + " something unexpected: " + change.getTaskInfo().taskId); continue; } - mPausingTasks.add(mOpeningTasks.remove(openingIdx)); + final TaskState openingTask = mOpeningTasks.remove(openingIdx); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " pausing opening taskId=%d", openingTask.mTaskInfo.taskId); + mPausingTasks.add(openingTask); didMergeThings = true; } } @@ -514,7 +549,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { // Something is showing/opening a previously-pausing app. appearedTargets[i] = TransitionUtil.newTarget( change, layer, mPausingTasks.get(pausingIdx).mLeash); - mOpeningTasks.add(mPausingTasks.remove(pausingIdx)); + final TaskState pausingTask = mPausingTasks.remove(pausingIdx); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " opening pausing taskId=%d", pausingTask.mTaskInfo.taskId); + mOpeningTasks.add(pausingTask); // Setup hides opening tasks initially, so make it visible again (since we // are already showing it). t.show(change.getLeash()); @@ -527,6 +565,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo); t.reparent(appearedTargets[i].leash, mInfo.getRoot(rootIdx).getLeash()); t.setLayer(appearedTargets[i].leash, layer); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " opening new taskId=%d", appearedTargets[i].taskId); mOpeningTasks.add(new TaskState(change, appearedTargets[i].leash)); } } @@ -544,7 +584,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { + foundRecentsClosing); if (foundRecentsClosing) { mWillFinishToHome = false; - cancel(false /* toHome */); + cancel(false /* toHome */, "didn't merge"); } return; } @@ -552,13 +592,15 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { t.apply(); // not using the incoming anim-only surfaces info.releaseAnimSurfaces(); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); if (appearedTargets == null) return; try { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.merge: calling onTasksAppeared"); mListener.onTasksAppeared(appearedTargets); } catch (RemoteException e) { Slog.e(TAG, "Error sending appeared tasks to recents animation", e); } + finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); } /** For now, just set-up a jump-cut to the new activity. */ @@ -577,6 +619,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { @Override public TaskSnapshot screenshotTask(int taskId) { try { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.screenshotTask: taskId=%d", taskId); return ActivityTaskManager.getService().takeTaskSnapshot(taskId); } catch (RemoteException e) { Slog.e(TAG, "Failed to screenshot task", e); @@ -587,12 +631,19 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { @Override public void setInputConsumerEnabled(boolean enabled) { mExecutor.execute(() -> { - if (mFinishCB == null || !enabled) return; + if (mFinishCB == null || !enabled) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.setInputConsumerEnabled: skip, cb?=%b enabled?=%b", + mFinishCB != null, enabled); + return; + } // transient launches don't receive focus automatically. Since we are taking over // the gesture now, take focus explicitly. // This also moves recents back to top if the user gestured before a switch // animation finished. try { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.setInputConsumerEnabled: set focus to recents"); ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId); } catch (RemoteException e) { Slog.e(TAG, "Failed to set focused task", e); @@ -607,6 +658,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { @Override public void setFinishTaskTransaction(int taskId, PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.setFinishTaskTransaction: taskId=%d", taskId); mExecutor.execute(() -> { if (mFinishCB == null) return; mPipTransaction = finishTransaction; @@ -624,6 +677,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { Slog.e(TAG, "Duplicate call to finish"); return; } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.finishInner: toHome=%b userLeaveHint=%b willFinishToHome=%b", + toHome, sendUserLeaveHint, mWillFinishToHome); final Transitions.TransitionFinishCallback finishCB = mFinishCB; mFinishCB = null; @@ -635,6 +691,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { else wct.restoreTransientOrder(mRecentsTask); } if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app"); // The gesture is returning to the pausing-task(s) rather than continuing with // recents, so end the transition by moving the app back to the top (and also // re-showing it's task). @@ -647,6 +704,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { wct.restoreTransientOrder(mRecentsTask); } } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " 3p launching home"); // Special situation where 3p launcher was changed during recents (this happens // during tapltests...). Here we get both "return to home" AND "home opening". // This is basically going home, but we have to restore the recents and home order. @@ -665,6 +723,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { wct.restoreTransientOrder(mRecentsTask); } } else { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " normal finish"); // The general case: committing to recents, going home, or switching tasks. for (int i = 0; i < mOpeningTasks.size(); ++i) { t.show(mOpeningTasks.get(i).mTaskSurface); @@ -721,6 +780,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { */ @Override public void detachNavigationBarFromApp(boolean moveHomeToTop) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.detachNavigationBarFromApp"); mExecutor.execute(() -> { if (mTransition == null) return; try { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index bdb7d44bad32..08b0bf74f413 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -429,6 +429,7 @@ public class Transitions implements RemoteCallable<Transitions> { && (change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) { t.setAlpha(leash, 0.f); } + finishT.show(leash); } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { finishT.hide(leash); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 8e8facadfd5d..e8a6a159cb19 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -127,7 +127,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { if (decoration == null) { createWindowDecoration(taskInfo, taskSurface, startT, finishT); } else { - decoration.relayout(taskInfo, startT, finishT); + decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */); } } @@ -139,7 +139,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); if (decoration == null) return; - decoration.relayout(taskInfo, startT, finishT); + decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */); } @Override @@ -192,7 +192,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); windowDecoration.setDragPositioningCallback(taskPositioner); windowDecoration.setDragDetector(touchEventListener.mDragDetector); - windowDecoration.relayout(taskInfo, startT, finishT); + windowDecoration.relayout(taskInfo, startT, finishT, + false /* applyStartTransactionOnDraw */); setupCaptionColor(taskInfo, windowDecoration); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index dfde7e6feff5..116af7094e13 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -90,15 +90,15 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL @Override void relayout(RunningTaskInfo taskInfo) { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - relayout(taskInfo, t, t); - mSyncQueue.runInSync(transaction -> { - transaction.merge(t); - t.close(); - }); + // Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is + // synced with the buffer transaction (that draws the View). Both will be shown on screen + // at the same, whereas applying them independently causes flickering. See b/270202228. + relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */); } void relayout(RunningTaskInfo taskInfo, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { + SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, + boolean applyStartTransactionOnDraw) { final int shadowRadiusID = taskInfo.isFocused ? R.dimen.freeform_decor_shadow_focused_thickness : R.dimen.freeform_decor_shadow_unfocused_thickness; @@ -115,6 +115,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mRelayoutParams.mLayoutResId = R.layout.caption_window_decor; mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; mRelayoutParams.mShadowRadiusId = shadowRadiusID; + mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index e137bc462607..49a5eac5fd07 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -247,7 +247,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (decoration == null) { createWindowDecoration(taskInfo, taskSurface, startT, finishT); } else { - decoration.relayout(taskInfo, startT, finishT); + decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */); } } @@ -259,7 +259,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); if (decoration == null) return; - decoration.relayout(taskInfo, startT, finishT); + decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */); } @Override @@ -781,7 +781,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); windowDecoration.setDragPositioningCallback(taskPositioner); windowDecoration.setDragDetector(touchEventListener.mDragDetector); - windowDecoration.relayout(taskInfo, startT, finishT); + windowDecoration.relayout(taskInfo, startT, finishT, + false /* applyStartTransactionOnDraw */); incrementEventReceiverTasks(taskInfo.displayId); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 67e99d73b811..af3fb0ead55c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -173,15 +173,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @Override void relayout(ActivityManager.RunningTaskInfo taskInfo) { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - relayout(taskInfo, t, t); - mSyncQueue.runInSync(transaction -> { - transaction.merge(t); - t.close(); - }); + // Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is + // synced with the buffer transaction (that draws the View). Both will be shown on screen + // at the same, whereas applying them independently causes flickering. See b/270202228. + relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */); } void relayout(ActivityManager.RunningTaskInfo taskInfo, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { + SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, + boolean applyStartTransactionOnDraw) { final int shadowRadiusID = taskInfo.isFocused ? R.dimen.freeform_decor_shadow_focused_thickness : R.dimen.freeform_decor_shadow_unfocused_thickness; @@ -216,6 +216,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mRelayoutParams.mLayoutResId = windowDecorLayoutId; mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; mRelayoutParams.mShadowRadiusId = shadowRadiusID; + mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 8b35694ff8fd..9a1b4ffbd50c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -238,30 +238,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) .show(mCaptionContainerSurface); - if (mCaptionWindowManager == null) { - // Put caption under a container surface because ViewRootImpl sets the destination frame - // of windowless window layers and BLASTBufferQueue#update() doesn't support offset. - mCaptionWindowManager = new WindowlessWindowManager( - mTaskInfo.getConfiguration(), mCaptionContainerSurface, - null /* hostInputToken */); - } - - // Caption view - mCaptionWindowManager.setConfiguration(taskConfig); - final WindowManager.LayoutParams lp = - new WindowManager.LayoutParams(captionWidth, captionHeight, - WindowManager.LayoutParams.TYPE_APPLICATION, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); - lp.setTitle("Caption of Task=" + mTaskInfo.taskId); - lp.setTrustedOverlay(); - if (mViewHost == null) { - mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay, - mCaptionWindowManager); - mViewHost.setView(outResult.mRootView, lp); - } else { - mViewHost.relayout(lp); - } - if (ViewRootImpl.CAPTION_ON_SHELL) { outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused); @@ -287,6 +263,36 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .show(mTaskSurface); finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y) .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); + + if (mCaptionWindowManager == null) { + // Put caption under a container surface because ViewRootImpl sets the destination frame + // of windowless window layers and BLASTBufferQueue#update() doesn't support offset. + mCaptionWindowManager = new WindowlessWindowManager( + mTaskInfo.getConfiguration(), mCaptionContainerSurface, + null /* hostInputToken */); + } + + // Caption view + mCaptionWindowManager.setConfiguration(taskConfig); + final WindowManager.LayoutParams lp = + new WindowManager.LayoutParams(captionWidth, captionHeight, + WindowManager.LayoutParams.TYPE_APPLICATION, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); + lp.setTitle("Caption of Task=" + mTaskInfo.taskId); + lp.setTrustedOverlay(); + if (mViewHost == null) { + mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay, + mCaptionWindowManager); + if (params.mApplyStartTransactionOnDraw) { + mViewHost.getRootSurfaceControl().applyTransactionOnDraw(startT); + } + mViewHost.setView(outResult.mRootView, lp); + } else { + if (params.mApplyStartTransactionOnDraw) { + mViewHost.getRootSurfaceControl().applyTransactionOnDraw(startT); + } + mViewHost.relayout(lp); + } } /** @@ -411,6 +417,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> int mCaptionX; int mCaptionY; + boolean mApplyStartTransactionOnDraw; + void reset() { mLayoutResId = Resources.ID_NULL; mCaptionHeightId = Resources.ID_NULL; @@ -419,6 +427,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mCaptionX = 0; mCaptionY = 0; + + mApplyStartTransactionOnDraw = false; } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index c416ad011c4e..45024f387c25 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -223,15 +223,6 @@ fun FlickerTest.splitAppLayerBoundsChanges( portraitPosTop, scenario.endRotation ) - .then() - .isInvisible(component) - .then() - .splitAppLayerBoundsSnapToDivider( - component, - landscapePosLeft, - portraitPosTop, - scenario.endRotation - ) } else { splitAppLayerBoundsSnapToDivider( component, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt index 62936e0f5ca8..625987a2e7ef 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt @@ -293,7 +293,7 @@ internal object SplitScreenUtils { wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace ?: error("Display not found") val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) - dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 2000) + dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200) wmHelper .StateSyncBuilder() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index d95c7a488ea1..3d8bd3854a45 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -135,12 +135,15 @@ public class BackAnimationControllerTest extends ShellTestCase { mShellExecutor.flushAll(); } - private void createNavigationInfo(int backType, boolean enableAnimation) { + private void createNavigationInfo(int backType, + boolean enableAnimation, + boolean isAnimationCallback) { BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder() .setType(backType) .setOnBackNavigationDone(new RemoteCallback((bundle) -> {})) .setOnBackInvokedCallback(mAppCallback) - .setPrepareRemoteAnimation(enableAnimation); + .setPrepareRemoteAnimation(enableAnimation) + .setAnimationCallback(isAnimationCallback); createNavigationInfo(builder); } @@ -218,7 +221,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test public void backToHome_dispatchesEvents() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); - createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, + /* enableAnimation = */ true, + /* isAnimationCallback = */ false); doMotionEvent(MotionEvent.ACTION_DOWN, 0); @@ -240,6 +245,32 @@ public class BackAnimationControllerTest extends ShellTestCase { } @Test + public void backToHomeWithAnimationCallback_dispatchesEvents() throws RemoteException { + registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, + /* enableAnimation = */ true, + /* isAnimationCallback = */ true); + + doMotionEvent(MotionEvent.ACTION_DOWN, 0); + + // Check that back start and progress is dispatched when first move. + doMotionEvent(MotionEvent.ACTION_MOVE, 100, 3000); + + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); + + verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class)); + verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); + ArgumentCaptor<BackMotionEvent> backEventCaptor = + ArgumentCaptor.forClass(BackMotionEvent.class); + verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture()); + + // Check that back invocation is dispatched. + mController.setTriggerBack(true); // Fake trigger back + doMotionEvent(MotionEvent.ACTION_UP, 0); + verify(mAnimatorCallback).onBackInvoked(); + } + + @Test public void animationDisabledFromSettings() throws RemoteException { // Toggle the setting off Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0"); @@ -254,7 +285,9 @@ public class BackAnimationControllerTest extends ShellTestCase { ArgumentCaptor<BackMotionEvent> backEventCaptor = ArgumentCaptor.forClass(BackMotionEvent.class); - createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, + /* enableAnimation = */ false, + /* isAnimationCallback = */ false); triggerBackGesture(); releaseBackGesture(); @@ -271,7 +304,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test public void ignoresGesture_transitionInProgress() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); - createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, + /* enableAnimation = */ true, + /* isAnimationCallback = */ false); triggerBackGesture(); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); @@ -309,7 +344,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test public void acceptsGesture_transitionTimeout() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); - createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, + /* enableAnimation = */ true, + /* isAnimationCallback = */ false); // In case it is still running in animation. doNothing().when(mAnimatorCallback).onBackInvoked(); @@ -334,7 +371,9 @@ public class BackAnimationControllerTest extends ShellTestCase { public void cancelBackInvokeWhenLostFocus() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); - createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, + /* enableAnimation = */ true, + /* isAnimationCallback = */ false); doMotionEvent(MotionEvent.ACTION_DOWN, 0); // Check that back start and progress is dispatched when first move. @@ -454,7 +493,9 @@ public class BackAnimationControllerTest extends ShellTestCase { mController.registerAnimation(type, animationRunner); - createNavigationInfo(type, true); + createNavigationInfo(type, + /* enableAnimation = */ true, + /* isAnimationCallback = */ false); doMotionEvent(MotionEvent.ACTION_DOWN, 0); @@ -473,11 +514,15 @@ public class BackAnimationControllerTest extends ShellTestCase { } private void doMotionEvent(int actionDown, int coordinate) { + doMotionEvent(actionDown, coordinate, 0); + } + + private void doMotionEvent(int actionDown, int coordinate, float velocity) { mController.onMotionEvent( /* touchX */ coordinate, /* touchY */ coordinate, - /* velocityX = */ 0, - /* velocityY = */ 0, + /* velocityX = */ velocity, + /* velocityY = */ velocity, /* keyAction */ actionDown, /* swipeEdge */ BackEvent.EDGE_LEFT); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index 919bf0665b5e..4a55429eacb6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -16,8 +16,6 @@ package com.android.wm.shell.bubbles; -import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE; - import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -185,7 +183,10 @@ public class BubbleDataTest extends ShellTestCase { Intent appBubbleIntent = new Intent(mContext, BubblesTestActivity.class); appBubbleIntent.setPackage(mContext.getPackageName()); - mAppBubble = new Bubble(appBubbleIntent, new UserHandle(1), mock(Icon.class), + mAppBubble = Bubble.createAppBubble( + appBubbleIntent, + new UserHandle(1), + mock(Icon.class), mMainExecutor); mPositioner = new TestableBubblePositioner(mContext, @@ -1101,14 +1102,15 @@ public class BubbleDataTest extends ShellTestCase { @Test public void test_removeAppBubble_skipsOverflow() { + String appBubbleKey = mAppBubble.getKey(); mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/, false /* showInShade */); - assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isEqualTo(mAppBubble); + assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isEqualTo(mAppBubble); - mBubbleData.dismissBubbleWithKey(KEY_APP_BUBBLE, Bubbles.DISMISS_USER_GESTURE); + mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_USER_GESTURE); - assertThat(mBubbleData.getOverflowBubbleWithKey(KEY_APP_BUBBLE)).isNull(); - assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull(); + assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNull(); + assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isNull(); } private void verifyUpdateReceived() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index c1e53a90b7e0..fc4bfd9754a0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -33,6 +33,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.same; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.Context; @@ -42,6 +43,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.util.DisplayMetrics; +import android.view.AttachedSurfaceControl; import android.view.Display; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; @@ -97,6 +99,8 @@ public class WindowDecorationTests extends ShellTestCase { @Mock private SurfaceControlViewHost mMockSurfaceControlViewHost; @Mock + private AttachedSurfaceControl mMockRootSurfaceControl; + @Mock private TestView mMockView; @Mock private WindowContainerTransaction mMockWindowContainerTransaction; @@ -129,6 +133,8 @@ public class WindowDecorationTests extends ShellTestCase { doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory) .create(any(), any(), any()); + when(mMockSurfaceControlViewHost.getRootSurfaceControl()) + .thenReturn(mMockRootSurfaceControl); } @Test @@ -461,6 +467,43 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlStartT).show(captionContainerSurface); } + @Test + public void testRelayout_applyTransactionInSyncWithDraw() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + final SurfaceControl decorContainerSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder decorContainerSurfaceBuilder = + createMockSurfaceControlBuilder(decorContainerSurface); + mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); + final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder captionContainerSurfaceBuilder = + createMockSurfaceControlBuilder(captionContainerSurface); + mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); + + final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + mMockSurfaceControlTransactions.add(t); + final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = + new ActivityManager.TaskDescription.Builder() + .setBackgroundColor(Color.YELLOW); + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setTaskDescriptionBuilder(taskDescriptionBuilder) + .setBounds(TASK_BOUNDS) + .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y) + .setVisible(true) + .build(); + taskInfo.isFocused = true; + taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; + final SurfaceControl taskSurface = mock(SurfaceControl.class); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); + + windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */); + + verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT); + } + private TestWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) { return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(), @@ -516,6 +559,12 @@ public class WindowDecorationTests extends ShellTestCase { @Override void relayout(ActivityManager.RunningTaskInfo taskInfo) { + relayout(taskInfo, false /* applyStartTransactionOnDraw */); + } + + void relayout(ActivityManager.RunningTaskInfo taskInfo, + boolean applyStartTransactionOnDraw) { + mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mMockView, mRelayoutResult); } diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 70c36a5803ee..34af1f9ea039 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -399,7 +399,7 @@ cc_defaults { "libharfbuzz_ng", "libimage_io", "libjpeg", - "libjpegrecoverymap", + "libultrahdr", "liblog", "libminikin", "libz", diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp index 8874ef1d2fe0..69418b09fee6 100644 --- a/libs/hwui/jni/YuvToJpegEncoder.cpp +++ b/libs/hwui/jni/YuvToJpegEncoder.cpp @@ -298,39 +298,39 @@ void Yuv422IToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) { } /////////////////////////////////////////////////////////////////////////////// -using namespace android::jpegrecoverymap; +using namespace android::ultrahdr; -jpegr_color_gamut P010Yuv420ToJpegREncoder::findColorGamut(JNIEnv* env, int aDataSpace) { +ultrahdr_color_gamut P010Yuv420ToJpegREncoder::findColorGamut(JNIEnv* env, int aDataSpace) { switch (aDataSpace & ADataSpace::STANDARD_MASK) { case ADataSpace::STANDARD_BT709: - return jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; + return ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; case ADataSpace::STANDARD_DCI_P3: - return jpegr_color_gamut::JPEGR_COLORGAMUT_P3; + return ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_P3; case ADataSpace::STANDARD_BT2020: - return jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; + return ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; default: jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException"); env->ThrowNew(IllegalArgumentException, "The requested color gamut is not supported by JPEG/R."); } - return jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED; + return ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; } -jpegr_transfer_function P010Yuv420ToJpegREncoder::findHdrTransferFunction(JNIEnv* env, +ultrahdr_transfer_function P010Yuv420ToJpegREncoder::findHdrTransferFunction(JNIEnv* env, int aDataSpace) { switch (aDataSpace & ADataSpace::TRANSFER_MASK) { case ADataSpace::TRANSFER_ST2084: - return jpegr_transfer_function::JPEGR_TF_PQ; + return ultrahdr_transfer_function::ULTRAHDR_TF_PQ; case ADataSpace::TRANSFER_HLG: - return jpegr_transfer_function::JPEGR_TF_HLG; + return ultrahdr_transfer_function::ULTRAHDR_TF_HLG; default: jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException"); env->ThrowNew(IllegalArgumentException, "The requested HDR transfer function is not supported by JPEG/R."); } - return jpegr_transfer_function::JPEGR_TF_UNSPECIFIED; + return ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED; } bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env, @@ -344,13 +344,13 @@ bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env, return false; } - jpegr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace); - jpegr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace); - jpegr_transfer_function hdrTransferFunction = findHdrTransferFunction(env, hdrColorSpace); + ultrahdr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace); + ultrahdr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace); + ultrahdr_transfer_function hdrTransferFunction = findHdrTransferFunction(env, hdrColorSpace); - if (hdrColorGamut == jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED - || sdrColorGamut == jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED - || hdrTransferFunction == jpegr_transfer_function::JPEGR_TF_UNSPECIFIED) { + if (hdrColorGamut == ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED + || sdrColorGamut == ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED + || hdrTransferFunction == ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED) { return false; } diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h index d22a26c83567..8ef780547184 100644 --- a/libs/hwui/jni/YuvToJpegEncoder.h +++ b/libs/hwui/jni/YuvToJpegEncoder.h @@ -2,7 +2,7 @@ #define _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_ #include <android/data_space.h> -#include <jpegrecoverymap/jpegr.h> +#include <ultrahdr/jpegr.h> extern "C" { #include "jpeglib.h" @@ -103,7 +103,7 @@ public: * @param aDataSpace data space defined in data_space.h. * @return color gamut for JPEG/R. */ - static android::jpegrecoverymap::jpegr_color_gamut findColorGamut(JNIEnv* env, int aDataSpace); + static android::ultrahdr::ultrahdr_color_gamut findColorGamut(JNIEnv* env, int aDataSpace); /** Map data space (defined in DataSpace.java and data_space.h) to the transfer function * used in JPEG/R @@ -112,7 +112,7 @@ public: * @param aDataSpace data space defined in data_space.h. * @return color gamut for JPEG/R. */ - static android::jpegrecoverymap::jpegr_transfer_function findHdrTransferFunction( + static android::ultrahdr::ultrahdr_transfer_function findHdrTransferFunction( JNIEnv* env, int aDataSpace); }; |