diff options
54 files changed, 903 insertions, 135 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 63da0a231286..d37576092af2 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2916,6 +2916,14 @@ public class Notification implements Parcelable } } + if (isStyle(CallStyle.class) & extras != null) { + Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON); + if (callPerson != null) { + visitor.accept(callPerson.getIconUri()); + } + visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON)); + } + if (mBubbleMetadata != null) { visitIconUri(visitor, mBubbleMetadata.getIcon()); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 048289f56a0c..960d10adbfbb 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -2628,6 +2628,15 @@ public class PackageParser { return Build.VERSION_CODES.CUR_DEVELOPMENT; } + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + targetCode)) { + Slog.w(TAG, "Package requires development platform " + targetCode + + ", returning current version " + Build.VERSION.SDK_INT); + return Build.VERSION.SDK_INT; + } + // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { outError[0] = "Requires development platform " + targetCode @@ -2699,6 +2708,15 @@ public class PackageParser { return Build.VERSION_CODES.CUR_DEVELOPMENT; } + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + minCode)) { + Slog.w(TAG, "Package requires min development platform " + minCode + + ", returning current version " + Build.VERSION.SDK_INT); + return Build.VERSION.SDK_INT; + } + // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { outError[0] = "Requires development platform " + minCode diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java index 3e1c5bb3d7ec..8cc4cdb955ca 100644 --- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java @@ -316,6 +316,15 @@ public class FrameworkParsingPackageUtils { return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); } + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + minCode)) { + Slog.w(TAG, "Parsed package requires min development platform " + minCode + + ", returning current version " + Build.VERSION.SDK_INT); + return input.success(Build.VERSION.SDK_INT); + } + // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, @@ -368,19 +377,27 @@ public class FrameworkParsingPackageUtils { return input.success(targetVers); } + // If it's a pre-release SDK and the codename matches this platform, it + // definitely targets this SDK. + if (matchTargetCode(platformSdkCodenames, targetCode)) { + return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); + } + + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + targetCode)) { + Slog.w(TAG, "Parsed package requires development platform " + targetCode + + ", returning current version " + Build.VERSION.SDK_INT); + return input.success(Build.VERSION.SDK_INT); + } + try { if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) { return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); } } catch (IllegalArgumentException e) { - // isAtMost() throws it when encountering an older SDK codename - return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, e.getMessage()); - } - - // If it's a pre-release SDK and the codename matches this platform, it - // definitely targets this SDK. - if (matchTargetCode(platformSdkCodenames, targetCode)) { - return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); + return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, "Bad package SDK"); } // Otherwise, we're looking at an incompatible pre-release SDK. diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java index bc0e35da37c3..511cb2df712d 100644 --- a/core/java/android/util/IntArray.java +++ b/core/java/android/util/IntArray.java @@ -43,7 +43,7 @@ public class IntArray implements Cloneable { * Creates an empty IntArray with the default initial capacity. */ public IntArray() { - this(10); + this(0); } /** diff --git a/core/java/android/util/LongArray.java b/core/java/android/util/LongArray.java index 53dddeb5ad0b..9f269ed74048 100644 --- a/core/java/android/util/LongArray.java +++ b/core/java/android/util/LongArray.java @@ -48,7 +48,7 @@ public class LongArray implements Cloneable { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public LongArray() { - this(10); + this(0); } /** diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index 2b08a5525d82..853fe2f114f7 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -77,6 +77,10 @@ public class SystemUiSystemPropertiesFlags { /** Gating the removal of sorting-notifications-by-interruptiveness. */ public static final Flag NO_SORT_BY_INTERRUPTIVENESS = devFlag("persist.sysui.notification.no_sort_by_interruptiveness"); + + /** Gating the logging of DND state change events. */ + public static final Flag LOG_DND_STATE_EVENTS = + devFlag("persist.sysui.notification.log_dnd_state_events"); } //// == End of flags. Everything below this line is the implementation. == //// diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e62477f0dbe3..f6c9fabb7896 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1036,9 +1036,9 @@ android:protectionLevel="dangerous" /> <!-- @SystemApi @hide Allows an application to communicate over satellite. - Only granted if the application is a system app. --> + Only granted if the application is a system app or privileged app. --> <permission android:name="android.permission.SATELLITE_COMMUNICATION" - android:protectionLevel="internal|role" /> + android:protectionLevel="role|signature|privileged" /> <!-- ====================================================================== --> <!-- Permissions for accessing external storage --> diff --git a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java index a53d57f0383c..a102b3ed9971 100644 --- a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java +++ b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java @@ -1127,6 +1127,31 @@ public class ValueAnimatorTests { mActivityRule.runOnUiThread(() -> {}); } + @Test + public void restartValueAnimator() throws Throwable { + CountDownLatch latch = new CountDownLatch(1); + ValueAnimator.AnimatorUpdateListener listener = new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + if (((float) animation.getAnimatedValue()) != A1_START_VALUE) { + latch.countDown(); + } + } + }; + a1.addUpdateListener(listener); + + mActivityRule.runOnUiThread(() -> { + a1.start(); + }); + + // wait for a change in the value + assertTrue(latch.await(2, TimeUnit.SECONDS)); + + mActivityRule.runOnUiThread(() -> { + a1.start(); + assertEquals(A1_START_VALUE, a1.getAnimatedValue()); + }); + } class MyUpdateListener implements ValueAnimator.AnimatorUpdateListener { boolean wasRunning = false; long firstRunningFrameTime = -1; diff --git a/data/etc/Android.bp b/data/etc/Android.bp index f233c6eca13b..6a1f3f959185 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -54,6 +54,12 @@ prebuilt_etc { src: "hiddenapi-package-whitelist.xml", } +prebuilt_etc { + name: "preinstalled-packages-asl-files.xml", + sub_dir: "sysconfig", + src: "preinstalled-packages-asl-files.xml", +} + // Privapp permission whitelist files prebuilt_etc { diff --git a/data/etc/preinstalled-packages-asl-files.xml b/data/etc/preinstalled-packages-asl-files.xml new file mode 100644 index 000000000000..6b5401c921f1 --- /dev/null +++ b/data/etc/preinstalled-packages-asl-files.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- +This XML file declares which preinstalled apps have Android Security Label data by declaring the +path to the XML file containing this data. + +Example usage: + <asl-file package="com.foo.bar" path="/vendor/etc/asl/com.foo.bar.xml"/> +--> + +<config></config> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 8ece337971a9..40cb7f2194e9 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -518,6 +518,8 @@ applications that come with the platform <permission name="android.permission.LOG_FOREGROUND_RESOURCE_USE"/> <!-- Permission required for CTS test - CtsVoiceInteractionTestCases --> <permission name="android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER"/> + <!-- Permission required for CTS test - SatelliteManagerTest --> + <permission name="android.permission.SATELLITE_COMMUNICATION"/> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> 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/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index c8062bcf8519..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 @@ -1532,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); 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/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 a8b209fc7da6..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,27 +224,38 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { RecentsController(IRecentsAnimationRunner listener) { mListener = listener; - mDeathHandler = () -> 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); @@ -267,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); @@ -276,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; @@ -299,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; @@ -358,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)); @@ -372,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; } @@ -393,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; @@ -417,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; @@ -448,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. @@ -492,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; } } @@ -509,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()); @@ -522,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)); } } @@ -539,7 +584,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { + foundRecentsClosing); if (foundRecentsClosing) { mWillFinishToHome = false; - cancel(false /* toHome */); + cancel(false /* toHome */, "didn't merge"); } return; } @@ -549,6 +594,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { info.releaseAnimSurfaces(); 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); @@ -572,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); @@ -582,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); @@ -602,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; @@ -619,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; @@ -630,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). @@ -642,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. @@ -660,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); @@ -716,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/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/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index 4d2bb4c6016a..8b74d766a152 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -18,6 +18,7 @@ package com.android.credentialmanager import android.app.Activity import android.os.IBinder +import android.text.TextUtils import android.util.Log import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.result.ActivityResult @@ -67,9 +68,9 @@ class CredentialSelectorViewModel( var uiMetrics: UIMetrics = UIMetrics() - init{ + init { uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_INIT, - credManRepo.requestInfo?.appPackageName) + credManRepo.requestInfo?.appPackageName) } /**************************************************************************/ @@ -100,7 +101,7 @@ class CredentialSelectorViewModel( if (this.credManRepo.requestInfo?.token != credManRepo.requestInfo?.token) { this.uiMetrics.resetInstanceId() this.uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_NEW_REQUEST, - credManRepo.requestInfo?.appPackageName) + credManRepo.requestInfo?.appPackageName) } } @@ -174,7 +175,7 @@ class CredentialSelectorViewModel( private fun onInternalError() { Log.w(Constants.LOG_TAG, "UI closed due to illegal internal state") this.uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_INTERNAL_ERROR, - credManRepo.requestInfo?.appPackageName) + credManRepo.requestInfo?.appPackageName) credManRepo.onParsingFailureCancel() uiState = uiState.copy(dialogState = DialogState.COMPLETE) } @@ -314,10 +315,11 @@ class CredentialSelectorViewModel( uiState = uiState.copy( createCredentialUiState = uiState.createCredentialUiState?.copy( currentScreenState = - if (activeEntry.activeProvider.id == - userConfigRepo.getDefaultProviderId()) + if (activeEntry.activeProvider.id == userConfigRepo.getDefaultProviderId() || + !TextUtils.isEmpty(uiState.createCredentialUiState?.requestDisplayInfo + ?.appPreferredDefaultProviderId)) CreateScreenState.CREATION_OPTION_SELECTION - else CreateScreenState.MORE_OPTIONS_ROW_INTRO, + else CreateScreenState.DEFAULT_PROVIDER_CONFIRMATION, activeEntry = activeEntry ) ) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index 96010cc66821..9d871ed75494 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -139,12 +139,12 @@ fun CreateCredentialScreen( onRemoteEntrySelected = viewModel::createFlowOnEntrySelected, onLog = { viewModel.logUiEvent(it) }, ) - CreateScreenState.MORE_OPTIONS_ROW_INTRO -> { + CreateScreenState.DEFAULT_PROVIDER_CONFIRMATION -> { if (createCredentialUiState.activeEntry == null) { viewModel.onIllegalUiState("Expect active entry to be non-null" + " upon default provider dialog.") } else { - MoreOptionsRowIntroCard( + DefaultProviderConfirmationCard( selectedEntry = createCredentialUiState.activeEntry, onIllegalScreenState = viewModel::onIllegalUiState, onChangeDefaultSelected = @@ -420,7 +420,7 @@ fun MoreOptionsSelectionCard( } @Composable -fun MoreOptionsRowIntroCard( +fun DefaultProviderConfirmationCard( selectedEntry: ActiveEntry, onIllegalScreenState: (String) -> Unit, onChangeDefaultSelected: () -> Unit, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index 12bb6298b282..225dbf2b744f 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -126,6 +126,6 @@ enum class CreateScreenState { PROVIDER_SELECTION, CREATION_OPTION_SELECTION, MORE_OPTIONS_SELECTION, - MORE_OPTIONS_ROW_INTRO, + DEFAULT_PROVIDER_CONFIRMATION, EXTERNAL_ONLY_SELECTION, } diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java index 96ea5b45282b..27aade5e6bf8 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java @@ -19,6 +19,7 @@ package com.android.systemui.accessibility.accessibilitymenu; import android.Manifest; import android.accessibilityservice.AccessibilityButtonController; import android.accessibilityservice.AccessibilityService; +import android.app.KeyguardManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -82,6 +83,9 @@ public class AccessibilityMenuService extends AccessibilityService // TODO(b/136716947): Support multi-display once a11y framework side is ready. private DisplayManager mDisplayManager; + + private KeyguardManager mKeyguardManager; + private final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() { int mRotation; @@ -114,7 +118,7 @@ public class AccessibilityMenuService extends AccessibilityService private final BroadcastReceiver mToggleMenuReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - mA11yMenuLayout.toggleVisibility(); + toggleVisibility(); } }; @@ -159,10 +163,7 @@ public class AccessibilityMenuService extends AccessibilityService */ @Override public void onClicked(AccessibilityButtonController controller) { - if (SystemClock.uptimeMillis() - mLastTimeTouchedOutside - > BUTTON_CLICK_TIMEOUT) { - mA11yMenuLayout.toggleVisibility(); - } + toggleVisibility(); } /** @@ -209,6 +210,7 @@ public class AccessibilityMenuService extends AccessibilityService mDisplayManager = getSystemService(DisplayManager.class); mDisplayManager.registerDisplayListener(mDisplayListener, null); mAudioManager = getSystemService(AudioManager.class); + mKeyguardManager = getSystemService(KeyguardManager.class); sInitialized = true; } @@ -379,4 +381,12 @@ public class AccessibilityMenuService extends AccessibilityService } return false; } + + private void toggleVisibility() { + boolean locked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); + if (!locked && SystemClock.uptimeMillis() - mLastTimeTouchedOutside + > BUTTON_CLICK_TIMEOUT) { + mA11yMenuLayout.toggleVisibility(); + } + } } diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java index 7277392f1841..9d1af0e2375a 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java @@ -33,6 +33,7 @@ import static com.google.common.truth.Truth.assertThat; import android.accessibilityservice.AccessibilityServiceInfo; import android.app.Instrumentation; +import android.app.KeyguardManager; import android.app.UiAutomation; import android.content.BroadcastReceiver; import android.content.Context; @@ -44,6 +45,7 @@ import android.media.AudioManager; import android.os.PowerManager; import android.provider.Settings; import android.util.Log; +import android.view.Display; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -69,15 +71,19 @@ public class AccessibilityMenuServiceTest { private static final int CLICK_ID = AccessibilityNodeInfo.ACTION_CLICK; private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5; - private static final int TIMEOUT_UI_CHANGE_S = 10; + private static final int TIMEOUT_UI_CHANGE_S = 5; private static final int NO_GLOBAL_ACTION = -1; private static final String INPUT_KEYEVENT_KEYCODE_BACK = "input keyevent KEYCODE_BACK"; + private static final String TEST_PIN = "1234"; private static Instrumentation sInstrumentation; private static UiAutomation sUiAutomation; private static AtomicInteger sLastGlobalAction; private static AccessibilityManager sAccessibilityManager; + private static PowerManager sPowerManager; + private static KeyguardManager sKeyguardManager; + private static DisplayManager sDisplayManager; @BeforeClass public static void classSetup() throws Throwable { @@ -85,8 +91,14 @@ public class AccessibilityMenuServiceTest { sInstrumentation = InstrumentationRegistry.getInstrumentation(); sUiAutomation = sInstrumentation.getUiAutomation( UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES); + sUiAutomation.adoptShellPermissionIdentity( + UiAutomation.ALL_PERMISSIONS.toArray(new String[0])); + final Context context = sInstrumentation.getTargetContext(); sAccessibilityManager = context.getSystemService(AccessibilityManager.class); + sPowerManager = context.getSystemService(PowerManager.class); + sKeyguardManager = context.getSystemService(KeyguardManager.class); + sDisplayManager = context.getSystemService(DisplayManager.class); // Disable all a11yServices if any are active. if (!sAccessibilityManager.getEnabledAccessibilityServiceList( @@ -123,12 +135,16 @@ public class AccessibilityMenuServiceTest { @AfterClass public static void classTeardown() throws Throwable { + clearPin(); Settings.Secure.putString(sInstrumentation.getTargetContext().getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, ""); } @Before public void setup() throws Throwable { + clearPin(); + wakeUpScreen(); + sUiAutomation.executeShellCommand("input keyevent KEYCODE_MENU"); openMenu(); } @@ -138,11 +154,43 @@ public class AccessibilityMenuServiceTest { sLastGlobalAction.set(NO_GLOBAL_ACTION); } + private static void clearPin() throws Throwable { + sUiAutomation.executeShellCommand("locksettings clear --old " + TEST_PIN); + TestUtils.waitUntil("Device did not register as unlocked & insecure.", + TIMEOUT_SERVICE_STATUS_CHANGE_S, + () -> !sKeyguardManager.isDeviceSecure()); + } + + private static void setPin() throws Throwable { + sUiAutomation.executeShellCommand("locksettings set-pin " + TEST_PIN); + TestUtils.waitUntil("Device did not recognize as locked & secure.", + TIMEOUT_SERVICE_STATUS_CHANGE_S, + () -> sKeyguardManager.isDeviceSecure()); + } + private static boolean isMenuVisible() { AccessibilityNodeInfo root = sUiAutomation.getRootInActiveWindow(); return root != null && root.getPackageName().toString().equals(PACKAGE_NAME); } + private static void wakeUpScreen() throws Throwable { + sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP"); + TestUtils.waitUntil("Screen did not wake up.", + TIMEOUT_UI_CHANGE_S, + () -> sPowerManager.isInteractive()); + } + + private static void closeScreen() throws Throwable { + Display display = sDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); + setPin(); + sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN); + TestUtils.waitUntil("Screen did not close.", + TIMEOUT_UI_CHANGE_S, + () -> !sPowerManager.isInteractive() + && display.getState() == Display.STATE_OFF + ); + } + private static void openMenu() throws Throwable { Intent intent = new Intent(PACKAGE_NAME + INTENT_TOGGLE_MENU); sInstrumentation.getContext().sendBroadcast(intent); @@ -381,23 +429,24 @@ public class AccessibilityMenuServiceTest { @Test public void testOnScreenLock_closesMenu() throws Throwable { - openMenu(); - Context context = sInstrumentation.getTargetContext(); - PowerManager powerManager = context.getSystemService(PowerManager.class); - - assertThat(powerManager).isNotNull(); - assertThat(powerManager.isInteractive()).isTrue(); + closeScreen(); + wakeUpScreen(); - sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN); - TestUtils.waitUntil("Screen did not become locked", - TIMEOUT_UI_CHANGE_S, - () -> !powerManager.isInteractive()); + assertThat(isMenuVisible()).isFalse(); + } - sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP"); - TestUtils.waitUntil("Screen did not wake up", - TIMEOUT_UI_CHANGE_S, - () -> powerManager.isInteractive()); + @Test + public void testOnScreenLock_cannotOpenMenu() throws Throwable { + closeScreen(); + wakeUpScreen(); - assertThat(isMenuVisible()).isFalse(); + boolean timedOut = false; + try { + openMenu(); + } catch (AssertionError e) { + // Expected + timedOut = true; + } + assertThat(timedOut).isTrue(); } } diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt index 204bac88bc0d..450c6166905c 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -316,9 +316,9 @@ enum class Style(internal val coreSpec: CoreSpec) { ), CLOCK_VIBRANT( CoreSpec( - a1 = TonalSpec(HueSource(), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)), - a2 = TonalSpec(HueAdd(20.0), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)), - a3 = TonalSpec(HueAdd(60.0), ChromaBound(ChromaSource(), 30.0, Chroma.MAX_VALUE)), + a1 = TonalSpec(HueSource(), ChromaBound(ChromaSource(), 70.0, Chroma.MAX_VALUE)), + a2 = TonalSpec(HueAdd(20.0), ChromaBound(ChromaSource(), 70.0, Chroma.MAX_VALUE)), + a3 = TonalSpec(HueAdd(60.0), ChromaBound(ChromaSource(), 70.0, Chroma.MAX_VALUE)), // Not Used n1 = TonalSpec(HueSource(), ChromaConstant(0.0)), diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 76e051ea25f3..693268d730a4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -178,6 +178,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard @Override public void onUserInput() { + mKeyguardFaceAuthInteractor.onPrimaryBouncerUserInput(); mUpdateMonitor.cancelFaceAuth(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index e0af5ceedb2f..7a2013e2c612 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1957,11 +1957,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, if (mShowing && mKeyguardStateController.isShowing()) { if (mPM.isInteractive() && !mHiding) { // It's already showing, and we're not trying to show it while the screen is off. - // We can simply reset all of the views. + // We can simply reset all of the views, but don't hide the bouncer in case the user + // is currently interacting with it. if (DEBUG) Log.d(TAG, "doKeyguard: not showing (instead, resetting) because it is " + "already showing, we're interactive, and we were not previously hiding. " + "It should be safe to short-circuit here."); - resetStateLocked(); + resetStateLocked(/* hideBouncer= */ false); return; } else { // We are trying to show the keyguard while the screen is off or while we were in @@ -2035,8 +2036,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, * @see #handleReset */ private void resetStateLocked() { + resetStateLocked(/* hideBouncer= */ true); + } + + private void resetStateLocked(boolean hideBouncer) { if (DEBUG) Log.e(TAG, "resetStateLocked"); - Message msg = mHandler.obtainMessage(RESET); + Message msg = mHandler.obtainMessage(RESET, hideBouncer ? 1 : 0, 0); mHandler.sendMessage(msg); } @@ -2226,7 +2231,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, handleHide(); break; case RESET: - handleReset(); + handleReset(msg.arg1 != 0); break; case VERIFY_UNLOCK: Trace.beginSection("KeyguardViewMediator#handleMessage VERIFY_UNLOCK"); @@ -3008,10 +3013,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, * Handle message sent by {@link #resetStateLocked} * @see #RESET */ - private void handleReset() { + private void handleReset(boolean hideBouncer) { synchronized (KeyguardViewMediator.this) { if (DEBUG) Log.d(TAG, "handleReset"); - mKeyguardViewControllerLazy.get().reset(true /* hideBouncerWhenShowing */); + mKeyguardViewControllerLazy.get().reset(hideBouncer); } scheduleNonStrongBiometricIdleTimeout(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index dc5ac88472e4..5f2178df4346 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -225,10 +225,17 @@ constructor( } private fun observeFaceAuthResettingConditions() { - // Clear auth status when keyguard is going away or when the user is switching. - merge(keyguardRepository.isKeyguardGoingAway, userRepository.userSwitchingInProgress) - .onEach { goingAwayOrUserSwitchingInProgress -> - if (goingAwayOrUserSwitchingInProgress) { + // Clear auth status when keyguard is going away or when the user is switching or device + // starts going to sleep. + merge( + keyguardRepository.wakefulness.map { + WakefulnessModel.isSleepingOrStartingToSleep(it) + }, + keyguardRepository.isKeyguardGoingAway, + userRepository.userSwitchingInProgress + ) + .onEach { anyOfThemIsTrue -> + if (anyOfThemIsTrue) { _isAuthenticated.value = false retryCount = 0 halErrorRetryJob?.cancel() @@ -248,8 +255,8 @@ constructor( "nonStrongBiometricIsNotAllowed", faceDetectLog ), - // We don't want to run face detect if it's not possible to authenticate with FP - // from the bouncer. UDFPS is the only fp sensor type that won't support this. + // We don't want to run face detect if fingerprint can be used to unlock the device + // but it's not possible to authenticate with FP from the bouncer (UDFPS) logAndObserve( and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(), "udfpsAuthIsNotPossibleAnymore", @@ -306,7 +313,7 @@ constructor( logAndObserve( combine( keyguardInteractor.isSecureCameraActive, - alternateBouncerInteractor.isVisible, + alternateBouncerInteractor.isVisible ) { a, b -> !a || b }, @@ -334,12 +341,12 @@ constructor( logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog), logAndObserve( deviceEntryFingerprintAuthRepository.isLockedOut.isFalse(), - "fpLockedOut", + "fpIsNotLockedOut", faceAuthLog ), logAndObserve( trustRepository.isCurrentUserTrusted.isFalse(), - "currentUserTrusted", + "currentUserIsNotTrusted", faceAuthLog ), logAndObserve( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt index 06ae11fe810c..74ef7a50fd44 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt @@ -59,6 +59,7 @@ interface KeyguardFaceAuthInteractor { fun onQsExpansionStared() fun onNotificationPanelClicked() fun onSwipeUpOnBouncer() + fun onPrimaryBouncerUserInput() } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt index cad40aac00d3..5005b6c7f0df 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt @@ -59,4 +59,5 @@ class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInt override fun onNotificationPanelClicked() {} override fun onSwipeUpOnBouncer() {} + override fun onPrimaryBouncerUserInput() {} } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index 20ebb711c42d..6b515dab79f6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -151,6 +151,10 @@ constructor( return featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR) } + override fun onPrimaryBouncerUserInput() { + repository.cancel() + } + /** Provide the status of face authentication */ override val authenticationStatus = repository.authenticationStatus diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 7edb37832c4f..077ee027d764 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -136,6 +136,14 @@ public class LogModule { return factory.create("NotifRemoteInputLog", 50 /* maxSize */, false /* systrace */); } + /** Provides a logging buffer for all logs related to unseen notifications. */ + @Provides + @SysUISingleton + @UnseenNotificationLog + public static LogBuffer provideUnseenNotificationLogBuffer(LogBufferFactory factory) { + return factory.create("UnseenNotifLog", 20 /* maxSize */, false /* systrace */); + } + /** Provides a logging buffer for all logs related to the data layer of notifications. */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/UnseenNotificationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/UnseenNotificationLog.java new file mode 100644 index 000000000000..5c2321be4388 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/UnseenNotificationLog.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.plugins.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for unseen notification related messages. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface UnseenNotificationLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 9e204e48da8e..11c8c503899e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2809,6 +2809,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void setBouncerShowing(boolean bouncerShowing) { mBouncerShowing = bouncerShowing; + mNotificationStackScrollLayoutController.updateShowEmptyShadeView(); updateVisibility(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt index 4cbbefe1cd73..2fa070ca20b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt @@ -21,8 +21,10 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.os.UserHandle import android.provider.Settings import androidx.annotation.VisibleForTesting +import com.android.systemui.Dumpable import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -42,8 +44,14 @@ import com.android.systemui.statusbar.notification.collection.provider.SeenNotif import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.headsUpEvents +import com.android.systemui.util.asIndenting +import com.android.systemui.util.indentIfPossible import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import java.io.PrintWriter +import javax.inject.Inject +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -57,13 +65,11 @@ import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch import kotlinx.coroutines.yield -import javax.inject.Inject -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds /** * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section @@ -74,17 +80,19 @@ class KeyguardCoordinator @Inject constructor( @Background private val bgDispatcher: CoroutineDispatcher, + private val dumpManager: DumpManager, private val headsUpManager: HeadsUpManager, private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider, private val keyguardRepository: KeyguardRepository, private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val logger: KeyguardCoordinatorLogger, private val notifPipelineFlags: NotifPipelineFlags, @Application private val scope: CoroutineScope, private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider, private val secureSettings: SecureSettings, private val seenNotifsProvider: SeenNotificationsProviderImpl, private val statusBarStateController: StatusBarStateController, -) : Coordinator { +) : Coordinator, Dumpable { private val unseenNotifications = mutableSetOf<NotificationEntry>() private var unseenFilterEnabled = false @@ -103,6 +111,7 @@ constructor( pipeline.addCollectionListener(collectionListener) scope.launch { trackUnseenNotificationsWhileUnlocked() } scope.launch { invalidateWhenUnseenSettingChanges() } + dumpManager.registerDumpable(this) } private suspend fun trackUnseenNotificationsWhileUnlocked() { @@ -122,14 +131,16 @@ constructor( // If the screen is turning off, stop tracking, but if that transition is // cancelled, then start again. emitAll( - keyguardTransitionRepository.transitions - .map { step -> !step.isScreenTurningOff } + keyguardTransitionRepository.transitions.map { step -> + !step.isScreenTurningOff + } ) } } // Prevent double emit of `false` caused by transition to AOD, followed by keyguard // showing .distinctUntilChanged() + .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) } // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is // showing again @@ -140,9 +151,11 @@ constructor( // set when unlocked awaitTimeSpentNotDozing(SEEN_TIMEOUT) clearUnseenOnBeginTracking = true + logger.logSeenOnLockscreen() } else { if (clearUnseenOnBeginTracking) { clearUnseenOnBeginTracking = false + logger.logAllMarkedSeenOnUnlock() unseenNotifications.clear() } unseenNotifFilter.invalidateList("keyguard no longer showing") @@ -166,6 +179,8 @@ constructor( .first() } + // Track "unseen" notifications, marking them as seen when either shade is expanded or the + // notification becomes heads up. private suspend fun trackUnseenNotifications() { coroutineScope { launch { clearUnseenNotificationsWhenShadeIsExpanded() } @@ -179,6 +194,7 @@ constructor( // keyguard transition and not the user expanding the shade yield() if (isExpanded) { + logger.logShadeExpanded() unseenNotifications.clear() } } @@ -190,6 +206,7 @@ constructor( .forEach { unseenNotifications.remove(it) } headsUpManager.headsUpEvents.collect { (entry, isHun) -> if (isHun) { + logger.logUnseenHun(entry.key) unseenNotifications.remove(entry) } } @@ -231,6 +248,7 @@ constructor( if ( keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded ) { + logger.logUnseenAdded(entry.key) unseenNotifications.add(entry) } } @@ -239,12 +257,15 @@ constructor( if ( keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded ) { + logger.logUnseenUpdated(entry.key) unseenNotifications.add(entry) } } override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { - unseenNotifications.remove(entry) + if (unseenNotifications.remove(entry)) { + logger.logUnseenRemoved(entry.key) + } } } @@ -272,6 +293,7 @@ constructor( }.also { hasFiltered -> hasFilteredAnyNotifs = hasFilteredAnyNotifs || hasFiltered } override fun onCleanup() { + logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs) seenNotifsProvider.hasFilteredOutSeenNotifications = hasFilteredAnyNotifs hasFilteredAnyNotifs = false } @@ -306,11 +328,25 @@ constructor( sectionHeaderVisibilityProvider.sectionHeadersVisible = showSections } + override fun dump(pw: PrintWriter, args: Array<out String>) = + with(pw.asIndenting()) { + println( + "seenNotifsProvider.hasFilteredOutSeenNotifications=" + + seenNotifsProvider.hasFilteredOutSeenNotifications + ) + println("unseen notifications:") + indentIfPossible { + for (notification in unseenNotifications) { + println(notification.key) + } + } + } + companion object { private const val TAG = "KeyguardCoordinator" private val SEEN_TIMEOUT = 5.seconds } } -private val TransitionStep.isScreenTurningOff: Boolean get() = - transitionState == TransitionState.STARTED && to != KeyguardState.GONE
\ No newline at end of file +private val TransitionStep.isScreenTurningOff: Boolean + get() = transitionState == TransitionState.STARTED && to != KeyguardState.GONE diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt new file mode 100644 index 000000000000..6503a6403eaa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator + +import com.android.systemui.log.dagger.UnseenNotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import javax.inject.Inject + +private const val TAG = "KeyguardCoordinator" + +class KeyguardCoordinatorLogger +@Inject +constructor( + @UnseenNotificationLog private val buffer: LogBuffer, +) { + fun logSeenOnLockscreen() = + buffer.log( + TAG, + LogLevel.DEBUG, + "Notifications on lockscreen will be marked as seen when unlocked." + ) + + fun logTrackingUnseen(trackingUnseen: Boolean) = + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { bool1 = trackingUnseen }, + messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." }, + ) + + fun logAllMarkedSeenOnUnlock() = + buffer.log( + TAG, + LogLevel.DEBUG, + "Notifications have been marked as seen now that device is unlocked." + ) + + fun logShadeExpanded() = + buffer.log( + TAG, + LogLevel.DEBUG, + "Notifications have been marked as seen due to shade expansion." + ) + + fun logUnseenAdded(key: String) = + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { str1 = key }, + messagePrinter = { "Unseen notif added: $str1" }, + ) + + fun logUnseenUpdated(key: String) = + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { str1 = key }, + messagePrinter = { "Unseen notif updated: $str1" }, + ) + + fun logUnseenRemoved(key: String) = + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { str1 = key }, + messagePrinter = { "Unseen notif removed: $str1" }, + ) + + fun logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs: Boolean) = + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { bool1 = hasFilteredAnyNotifs }, + messagePrinter = { "UI showing unseen filter treatment: $bool1" }, + ) + + fun logUnseenHun(key: String) = + buffer.log( + TAG, + LogLevel.DEBUG, + messageInitializer = { str1 = key }, + messagePrinter = { "Unseen notif has become heads up: $str1" }, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index e2f93ce62e46..4177263efbd0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1235,7 +1235,8 @@ public class NotificationStackScrollLayoutController { // Hide empty shade view when in transition to Keyguard. // That avoids "No Notifications" to blink when transitioning to AOD. // For more details, see: b/228790482 - && !isInTransitionToKeyguard(); + && !isInTransitionToKeyguard() + && !mCentralSurfaces.isBouncerShowing(); mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 0de9608b906f..8f58140bce43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -502,7 +502,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { TestableLooper.get(this).processAllMessages(); assertTrue(mViewMediator.isShowingAndNotOccluded()); - verify(mStatusBarKeyguardViewManager).reset(anyBoolean()); + verify(mStatusBarKeyguardViewManager).reset(false); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index ffc6ee2a5a31..fa40fc431b5f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -644,6 +644,58 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test + fun isAuthenticatedIsResetToFalseWhenDeviceStartsGoingToSleep() = + testScope.runTest { + initCollectors() + allPreconditionsToRunFaceAuthAreTrue() + + triggerFaceAuth(false) + + authenticationCallback.value.onAuthenticationSucceeded( + mock(FaceManager.AuthenticationResult::class.java) + ) + + assertThat(authenticated()).isTrue() + + keyguardRepository.setWakefulnessModel( + WakefulnessModel( + WakefulnessState.STARTING_TO_SLEEP, + isWakingUpOrAwake = false, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.POWER_BUTTON + ) + ) + + assertThat(authenticated()).isFalse() + } + + @Test + fun isAuthenticatedIsResetToFalseWhenDeviceGoesToSleep() = + testScope.runTest { + initCollectors() + allPreconditionsToRunFaceAuthAreTrue() + + triggerFaceAuth(false) + + authenticationCallback.value.onAuthenticationSucceeded( + mock(FaceManager.AuthenticationResult::class.java) + ) + + assertThat(authenticated()).isTrue() + + keyguardRepository.setWakefulnessModel( + WakefulnessModel( + WakefulnessState.ASLEEP, + isWakingUpOrAwake = false, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.POWER_BUTTON + ) + ) + + assertThat(authenticated()).isFalse() + } + + @Test fun isAuthenticatedIsResetToFalseWhenUserIsSwitching() = testScope.runTest { initCollectors() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index 3d1d2f46a65e..5da1a846fbfd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -279,6 +279,23 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { } @Test + fun faceAuthIsCancelledWhenUserInputOnPrimaryBouncer() = + testScope.runTest { + underTest.start() + + underTest.onSwipeUpOnBouncer() + + runCurrent() + assertThat(faceAuthRepository.isAuthRunning.value).isTrue() + + underTest.onPrimaryBouncerUserInput() + + runCurrent() + + assertThat(faceAuthRepository.isAuthRunning.value).isFalse() + } + + @Test fun faceAuthIsRequestedWhenSwipeUpOnBouncer() = testScope.runTest { underTest.start() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt index 8f07f8d1a099..c3f51233f59a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt @@ -24,6 +24,7 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.advanceTimeBy +import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -380,10 +381,12 @@ class KeyguardCoordinatorTest : SysuiTestCase() { val keyguardCoordinator = KeyguardCoordinator( testDispatcher, + mock<DumpManager>(), headsUpManager, keyguardNotifVisibilityProvider, keyguardRepository, keyguardTransitionRepository, + mock<KeyguardCoordinatorLogger>(), notifPipelineFlags, testScope.backgroundScope, sectionHeaderVisibilityProvider, diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 0bdb0c80d219..a3b4a0f51c75 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -194,7 +194,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityUserState.ServiceInfoChangeListener, AccessibilityWindowManager.AccessibilityEventSender, AccessibilitySecurityPolicy.AccessibilityUserManager, - SystemActionPerformer.SystemActionsChangedListener, ProxyManager.SystemSupport{ + SystemActionPerformer.SystemActionsChangedListener, + SystemActionPerformer.DisplayUpdateCallBack, ProxyManager.SystemSupport { private static final boolean DEBUG = false; @@ -1219,7 +1220,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private SystemActionPerformer getSystemActionPerformer() { if (mSystemActionPerformer == null) { mSystemActionPerformer = - new SystemActionPerformer(mContext, mWindowManagerService, null, this); + new SystemActionPerformer(mContext, mWindowManagerService, null, this, this); } return mSystemActionPerformer; } @@ -1443,17 +1444,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return; } if (!mVisibleBgUserIds.get(userId)) { - Slogf.wtf(LOG_TAG, "Cannot change current user to %d as it's not visible " - + "(mVisibleUsers=%s)", userId, mVisibleBgUserIds); + Slogf.wtf(LOG_TAG, "changeCurrentUserForTestAutomationIfNeededLocked(): cannot change " + + "current user to %d as it's not visible (mVisibleUsers=%s)", + userId, mVisibleBgUserIds); return; } if (mCurrentUserId == userId) { - Slogf.w(LOG_TAG, "NOT changing current user for test automation purposes as it is " - + "already %d", mCurrentUserId); + Slogf.d(LOG_TAG, "changeCurrentUserForTestAutomationIfNeededLocked(): NOT changing " + + "current user for test automation purposes as it is already %d", + mCurrentUserId); return; } - Slogf.i(LOG_TAG, "Changing current user from %d to %d for test automation purposes", - mCurrentUserId, userId); + Slogf.i(LOG_TAG, "changeCurrentUserForTestAutomationIfNeededLocked(): changing current user" + + " from %d to %d for test automation purposes", mCurrentUserId, userId); mRealCurrentUserId = mCurrentUserId; switchUser(userId); } @@ -1466,7 +1469,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub + "because device doesn't support visible background users"); return; } - Slogf.i(LOG_TAG, "Restoring current user to %d after using %d for test automation purposes", + if (mRealCurrentUserId == UserHandle.USER_CURRENT) { + Slogf.d(LOG_TAG, "restoreCurrentUserForTestAutomationIfNeededLocked(): ignoring " + + "because mRealCurrentUserId is already USER_CURRENT"); + return; + } + Slogf.i(LOG_TAG, "restoreCurrentUserForTestAutomationIfNeededLocked(): restoring current " + + "user to %d after using %d for test automation purposes", mRealCurrentUserId, mCurrentUserId); int currentUserId = mRealCurrentUserId; mRealCurrentUserId = UserHandle.USER_CURRENT; @@ -1611,6 +1620,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + @Override + // TODO(b/276459590): Remove when this is resolved at the virtual device/input level. + public void moveNonProxyTopFocusedDisplayToTopIfNeeded() { + mA11yWindowManager.moveNonProxyTopFocusedDisplayToTopIfNeeded(); + } + + @Override + // TODO(b/276459590): Remove when this is resolved at the virtual device/input level. + public int getLastNonProxyTopFocusedDisplayId() { + return mA11yWindowManager.getLastNonProxyTopFocusedDisplayId(); + } + @VisibleForTesting void notifySystemActionsChangedLocked(AccessibilityUserState userState) { for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index a8a536590004..78f07e4f2692 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -103,6 +103,9 @@ public class AccessibilityWindowManager { // The top focused display and window token updated with the callback of window lists change. private int mTopFocusedDisplayId; private IBinder mTopFocusedWindowToken; + + // The non-proxy display that most recently had top focus. + private int mLastNonProxyTopFocusedDisplayId; // The display has the accessibility focused window currently. private int mAccessibilityFocusedDisplayId = Display.INVALID_DISPLAY; @@ -451,6 +454,9 @@ public class AccessibilityWindowManager { } if (shouldUpdateWindowsLocked(forceSend, windows)) { mTopFocusedDisplayId = topFocusedDisplayId; + if (!isProxyed(topFocusedDisplayId)) { + mLastNonProxyTopFocusedDisplayId = topFocusedDisplayId; + } mTopFocusedWindowToken = topFocusedWindowToken; if (DEBUG) { Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): updating windows for " @@ -1141,6 +1147,21 @@ public class AccessibilityWindowManager { return false; } + private boolean isProxyed(int displayId) { + final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId); + return (observer != null && observer.mIsProxy); + } + + void moveNonProxyTopFocusedDisplayToTopIfNeeded() { + if (mHasProxy + && (mLastNonProxyTopFocusedDisplayId != mTopFocusedDisplayId)) { + mWindowManagerInternal.moveDisplayToTopIfAllowed(mLastNonProxyTopFocusedDisplayId); + } + } + int getLastNonProxyTopFocusedDisplayId() { + return mLastNonProxyTopFocusedDisplayId; + } + /** * Checks if we are tracking windows on specified display. * diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java index c89b9b851742..a13df475d25d 100644 --- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java +++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java @@ -72,6 +72,13 @@ public class SystemActionPerformer { } private final SystemActionsChangedListener mListener; + interface DisplayUpdateCallBack { + void moveNonProxyTopFocusedDisplayToTopIfNeeded(); + + int getLastNonProxyTopFocusedDisplayId(); + } + private final DisplayUpdateCallBack mDisplayUpdateCallBack; + private final Object mSystemActionLock = new Object(); // Resource id based ActionId -> RemoteAction @GuardedBy("mSystemActionLock") @@ -94,7 +101,7 @@ public class SystemActionPerformer { public SystemActionPerformer( Context context, WindowManagerInternal windowManagerInternal) { - this(context, windowManagerInternal, null, null); + this(context, windowManagerInternal, null, null, null); } // Used to mock ScreenshotHelper @@ -103,17 +110,19 @@ public class SystemActionPerformer { Context context, WindowManagerInternal windowManagerInternal, Supplier<ScreenshotHelper> screenshotHelperSupplier) { - this(context, windowManagerInternal, screenshotHelperSupplier, null); + this(context, windowManagerInternal, screenshotHelperSupplier, null, null); } public SystemActionPerformer( Context context, WindowManagerInternal windowManagerInternal, Supplier<ScreenshotHelper> screenshotHelperSupplier, - SystemActionsChangedListener listener) { + SystemActionsChangedListener listener, + DisplayUpdateCallBack callback) { mContext = context; mWindowManagerService = windowManagerInternal; mListener = listener; + mDisplayUpdateCallBack = callback; mScreenshotHelperSupplier = screenshotHelperSupplier; mLegacyHomeAction = new AccessibilityAction( @@ -245,6 +254,7 @@ public class SystemActionPerformer { final long identity = Binder.clearCallingIdentity(); try { synchronized (mSystemActionLock) { + mDisplayUpdateCallBack.moveNonProxyTopFocusedDisplayToTopIfNeeded(); // If a system action is registered with the given actionId, call the corresponding // RemoteAction. RemoteAction registeredAction = mRegisteredSystemActions.get(actionId); @@ -341,7 +351,7 @@ public class SystemActionPerformer { int source) { KeyEvent event = KeyEvent.obtain(downTime, time, action, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM, - source, null); + source, mDisplayUpdateCallBack.getLastNonProxyTopFocusedDisplayId(), null); mContext.getSystemService(InputManager.class) .injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); event.recycle(); diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java index eb718853a6ce..d9e25ef7dcdc 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java @@ -199,6 +199,9 @@ public class TouchState { case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: mLastTouchedWindowId = event.getWindowId(); break; + case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END: + mAms.moveNonProxyTopFocusedDisplayToTopIfNeeded(); + break; } } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index 2d3928ca5721..8f416082374e 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -2237,7 +2237,14 @@ public class TunerResourceManagerService extends SystemService implements IBinde } clearAllResourcesAndClientMapping(getClientProfile(clientId)); mClientProfiles.remove(clientId); - mListeners.remove(clientId); + + // it may be called by unregisterClientProfileInternal under test + synchronized (mLock) { + ResourcesReclaimListenerRecord record = mListeners.remove(clientId); + if (record != null) { + record.getListener().asBinder().unlinkToDeath(record, 0); + } + } } private void clearFrontendAndClientMapping(ClientProfile profile) { diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 184293e11002..5626aa7f075f 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -681,6 +681,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { final StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo(); removalInfo.taskId = task.mTaskId; removalInfo.playRevealAnimation = prepareAnimation + && task.getDisplayContent() != null && task.getDisplayInfo().state == Display.STATE_ON; final boolean playShiftUpAnimation = !task.inMultiWindowMode(); final ActivityRecord topActivity = task.topActivityContainsStartingWindow(); diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 492252314356..792ec2e92083 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -444,6 +444,11 @@ public abstract class WindowManagerInternal { public abstract IBinder getFocusedWindowTokenFromWindowStates(); /** + * Moves the given display to the top. + */ + public abstract void moveDisplayToTopIfAllowed(int displayId); + + /** * @return Whether the keyguard is engaged. */ public abstract boolean isKeyguardLocked(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 62b3c7cd1daf..8822193ab522 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7716,6 +7716,11 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void moveDisplayToTopIfAllowed(int displayId) { + WindowManagerService.this.moveDisplayToTopIfAllowed(displayId); + } + + @Override public boolean isKeyguardLocked() { return WindowManagerService.this.isKeyguardLocked(); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java index d9461aada4d3..b62dbcd526cb 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java @@ -108,6 +108,7 @@ public class SystemActionPerformerTest { @Mock private StatusBarManager mMockStatusBarManager; @Mock private ScreenshotHelper mMockScreenshotHelper; @Mock private SystemActionPerformer.SystemActionsChangedListener mMockListener; + @Mock private SystemActionPerformer.DisplayUpdateCallBack mMockCallback; @Before public void setup() { @@ -125,7 +126,7 @@ public class SystemActionPerformerTest { mMockContext, mMockWindowManagerInternal, () -> mMockScreenshotHelper, - mMockListener); + mMockListener, mMockCallback); } private void setupWithRealContext() { @@ -133,7 +134,7 @@ public class SystemActionPerformerTest { InstrumentationRegistry.getContext(), mMockWindowManagerInternal, () -> mMockScreenshotHelper, - mMockListener); + mMockListener, mMockCallback); } // We need below two help functions because AccessbilityAction.equals function only compares diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java index aca96ad20385..aad373fdaf95 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -445,14 +445,14 @@ public class SystemConfigTest { + " <library \n" + " name=\"foo\"\n" + " file=\"" + mFooJar + "\"\n" - + " on-bootclasspath-before=\"Q\"\n" + + " on-bootclasspath-before=\"A\"\n" + " on-bootclasspath-since=\"W\"\n" + " />\n\n" + " </permissions>"; parseSharedLibraries(contents); assertFooIsOnlySharedLibrary(); SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo"); - assertThat(entry.onBootclasspathBefore).isEqualTo("Q"); + assertThat(entry.onBootclasspathBefore).isEqualTo("A"); assertThat(entry.onBootclasspathSince).isEqualTo("W"); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 9cfdaa7cad0c..dd9f3cb3d343 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -5447,6 +5447,29 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testVisitUris_callStyle() { + Icon personIcon = Icon.createWithContentUri("content://media/person"); + Icon verificationIcon = Icon.createWithContentUri("content://media/verification"); + Person callingPerson = new Person.Builder().setName("Someone") + .setIcon(personIcon) + .build(); + PendingIntent hangUpIntent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); + Notification n = new Notification.Builder(mContext, "a") + .setStyle(Notification.CallStyle.forOngoingCall(callingPerson, hangUpIntent) + .setVerificationIcon(verificationIcon)) + .setContentTitle("Calling...") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor, times(1)).accept(eq(personIcon.getUri())); + verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); + } + + @Test public void testVisitUris_audioContentsString() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); diff --git a/tools/validatekeymaps/Android.bp b/tools/validatekeymaps/Android.bp index 25373f9e9e2f..554f64e73b74 100644 --- a/tools/validatekeymaps/Android.bp +++ b/tools/validatekeymaps/Android.bp @@ -15,7 +15,7 @@ package { cc_binary_host { name: "validatekeymaps", - + cpp_std: "c++20", srcs: ["Main.cpp"], cflags: [ |