summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java70
-rw-r--r--core/api/test-current.txt2
-rw-r--r--core/java/android/animation/Animator.java4
-rw-r--r--core/java/android/animation/AnimatorInflater.java23
-rw-r--r--core/java/android/animation/AnimatorSet.java92
-rw-r--r--core/java/android/animation/ValueAnimator.java28
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java7
-rw-r--r--core/java/android/content/res/ConfigurationBoundResourceCache.java12
-rw-r--r--core/java/android/content/res/DrawableCache.java24
-rw-r--r--core/java/android/content/res/ResourcesImpl.java32
-rw-r--r--core/java/android/content/res/ThemedResourceCache.java47
-rw-r--r--core/java/android/hardware/display/DisplayManager.java4
-rw-r--r--core/java/android/print/PrintManager.java7
-rw-r--r--core/java/android/view/InsetsController.java16
-rw-r--r--core/java/android/view/InsetsFrameProvider.java13
-rw-r--r--core/java/android/view/WindowManager.java42
-rw-r--r--core/java/android/view/autofill/AutofillFeatureFlags.java6
-rw-r--r--core/java/android/view/textclassifier/TextClassification.java4
-rw-r--r--core/java/android/window/TransitionInfo.java7
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java40
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java4
-rw-r--r--core/res/res/layout/alert_dialog_button_bar_leanback.xml4
-rw-r--r--core/res/res/values/strings.xml4
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java37
-rw-r--r--core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java77
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java39
-rw-r--r--data/etc/services.core.protolog.json6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java121
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt64
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt56
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt25
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt25
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt28
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt40
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt50
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt52
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java12
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt4
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt3
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt11
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt34
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Utils.java8
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java23
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt12
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt23
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt16
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt44
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt4
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt4
-rw-r--r--packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml20
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_contents.xml8
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_icon.xml26
-rw-r--r--packages/SystemUI/res/values/dimens.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/res/xml/qs_header.xml4
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt16
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintIconViewBinder.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java105
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt109
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt243
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java113
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt79
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java164
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt278
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt283
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java79
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt153
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt39
-rw-r--r--services/autofill/java/com/android/server/autofill/LogFieldClassificationScoreOnResultListener.java79
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java145
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java57
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java6
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueImpl.java18
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java14
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java6
-rw-r--r--services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java21
-rw-r--r--services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java4
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java5
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java7
-rw-r--r--services/core/java/com/android/server/am/UidObserverController.java32
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java6
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java2
-rw-r--r--[-rwxr-xr-x]services/core/java/com/android/server/notification/NotificationManagerService.java72
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java6
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java2
-rw-r--r--services/core/java/com/android/server/pm/ResilientAtomicFile.java4
-rw-r--r--services/core/java/com/android/server/pm/SuspendPackageHelper.java4
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java9
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java118
-rw-r--r--services/core/java/com/android/server/wm/AbsAppSnapshotController.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartInterceptor.java18
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/AppWarnings.java9
-rw-r--r--services/core/java/com/android/server/wm/DeviceStateController.java8
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java11
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java4
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java23
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java22
-rw-r--r--services/core/java/com/android/server/wm/Transition.java4
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java8
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java33
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java38
-rw-r--r--services/print/java/com/android/server/print/UserState.java8
-rw-r--r--services/tests/PackageManagerServiceTests/server/Android.bp4
-rw-r--r--services/tests/PackageManagerServiceTests/server/AndroidTest.xml8
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java3
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java28
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt5
-rw-r--r--services/tests/servicestests/Android.bp1
-rw-r--r--services/tests/servicestests/AndroidTest.xml5
-rw-r--r--services/tests/servicestests/src/com/android/server/credentials/MetricUtilitiesTest.java116
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java118
-rw-r--r--services/tests/uiservicestests/AndroidManifest.xml2
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java186
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java39
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java23
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java9
-rw-r--r--tests/InputMethodStressTest/AndroidTest.xml1
201 files changed, 4281 insertions, 1308 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 4f5eb37d871d..cbc9263a2c3d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1631,7 +1631,8 @@ public class JobSchedulerService extends com.android.server.SystemService
jobStatus.getEstimatedNetworkDownloadBytes(),
jobStatus.getEstimatedNetworkUploadBytes(),
jobStatus.getWorkCount(),
- ActivityManager.processStateAmToProto(mUidProcStates.get(jobStatus.getUid())));
+ ActivityManager.processStateAmToProto(mUidProcStates.get(jobStatus.getUid())),
+ jobStatus.getNamespaceHash());
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2059,7 +2060,8 @@ public class JobSchedulerService extends com.android.server.SystemService
cancelled.getEstimatedNetworkDownloadBytes(),
cancelled.getEstimatedNetworkUploadBytes(),
cancelled.getWorkCount(),
- ActivityManager.processStateAmToProto(mUidProcStates.get(cancelled.getUid())));
+ ActivityManager.processStateAmToProto(mUidProcStates.get(cancelled.getUid())),
+ cancelled.getNamespaceHash());
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 90f1523104ee..0b08b6faf971 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -499,7 +499,8 @@ public final class JobServiceContext implements ServiceConnection {
job.getEstimatedNetworkDownloadBytes(),
job.getEstimatedNetworkUploadBytes(),
job.getWorkCount(),
- ActivityManager.processStateAmToProto(mService.getUidProcState(job.getUid())));
+ ActivityManager.processStateAmToProto(mService.getUidProcState(job.getUid())),
+ job.getNamespaceHash());
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1557,7 +1558,8 @@ public final class JobServiceContext implements ServiceConnection {
completedJob.getEstimatedNetworkUploadBytes(),
completedJob.getWorkCount(),
ActivityManager
- .processStateAmToProto(mService.getUidProcState(completedJob.getUid())));
+ .processStateAmToProto(mService.getUidProcState(completedJob.getUid())),
+ completedJob.getNamespaceHash());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index a8b4c695889c..edd531d13965 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -43,6 +43,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.MediaStore;
import android.text.format.DateFormat;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Pair;
@@ -51,6 +52,7 @@ import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
@@ -65,10 +67,12 @@ import com.android.server.job.JobStatusShortInfoProto;
import dalvik.annotation.optimization.NeverCompile;
import java.io.PrintWriter;
+import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
+import java.util.Random;
import java.util.function.Predicate;
/**
@@ -88,6 +92,13 @@ public final class JobStatus {
private static final String TAG = "JobScheduler.JobStatus";
static final boolean DEBUG = JobSchedulerService.DEBUG;
+ private static MessageDigest sMessageDigest;
+ /** Cache of namespace to hash to reduce how often we need to generate the namespace hash. */
+ @GuardedBy("sNamespaceHashCache")
+ private static final ArrayMap<String, String> sNamespaceHashCache = new ArrayMap<>();
+ /** Maximum size of {@link #sNamespaceHashCache}. */
+ private static final int MAX_NAMESPACE_CACHE_SIZE = 128;
+
private static final int NUM_CONSTRAINT_CHANGE_HISTORY = 10;
public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
@@ -231,6 +242,8 @@ public final class JobStatus {
final String sourceTag;
@Nullable
private final String mNamespace;
+ @Nullable
+ private final String mNamespaceHash;
/** An ID that can be used to uniquely identify the job when logging statsd metrics. */
private final long mLoggingJobId;
@@ -570,6 +583,7 @@ public final class JobStatus {
this.callingUid = callingUid;
this.standbyBucket = standbyBucket;
mNamespace = namespace;
+ mNamespaceHash = generateNamespaceHash(namespace);
mLoggingJobId = generateLoggingId(namespace, job.getId());
int tempSourceUid = -1;
@@ -814,6 +828,56 @@ public final class JobStatus {
return ((long) namespace.hashCode()) << 31 | jobId;
}
+ @Nullable
+ private static String generateNamespaceHash(@Nullable String namespace) {
+ if (namespace == null) {
+ return null;
+ }
+ if (namespace.trim().isEmpty()) {
+ // Input is composed of all spaces (or nothing at all).
+ return namespace;
+ }
+ synchronized (sNamespaceHashCache) {
+ final int idx = sNamespaceHashCache.indexOfKey(namespace);
+ if (idx >= 0) {
+ return sNamespaceHashCache.valueAt(idx);
+ }
+ }
+ String hash = null;
+ try {
+ // .hashCode() can result in conflicts that would make distinguishing between
+ // namespaces hard and reduce the accuracy of certain metrics. Use SHA-256
+ // to generate the hash since the probability of collision is extremely low.
+ if (sMessageDigest == null) {
+ sMessageDigest = MessageDigest.getInstance("SHA-256");
+ }
+ final byte[] digest = sMessageDigest.digest(namespace.getBytes());
+ // Convert to hexadecimal representation
+ StringBuilder hexBuilder = new StringBuilder(digest.length);
+ for (byte byteChar : digest) {
+ hexBuilder.append(String.format("%02X", byteChar));
+ }
+ hash = hexBuilder.toString();
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Couldn't hash input", e);
+ }
+ if (hash == null) {
+ // If we get to this point, something went wrong with the MessageDigest above.
+ // Don't return the raw input value (which would defeat the purpose of hashing).
+ return "failed_namespace_hash";
+ }
+ hash = hash.intern();
+ synchronized (sNamespaceHashCache) {
+ if (sNamespaceHashCache.size() >= MAX_NAMESPACE_CACHE_SIZE) {
+ // Drop a random mapping instead of dropping at a predefined index to avoid
+ // potentially always dropping the same mapping.
+ sNamespaceHashCache.removeAt((new Random()).nextInt(MAX_NAMESPACE_CACHE_SIZE));
+ }
+ sNamespaceHashCache.put(namespace, hash);
+ }
+ return hash;
+ }
+
public void enqueueWorkLocked(JobWorkItem work) {
if (pendingWork == null) {
pendingWork = new ArrayList<>();
@@ -1117,10 +1181,16 @@ public final class JobStatus {
return true;
}
+ @Nullable
public String getNamespace() {
return mNamespace;
}
+ @Nullable
+ public String getNamespaceHash() {
+ return mNamespaceHash;
+ }
+
public String getSourceTag() {
return sourceTag;
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9eb9d66cb71a..9bd29700a12d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3568,6 +3568,8 @@ package android.view {
field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000
field public CharSequence accessibilityTitle;
+ field public float preferredMaxDisplayRefreshRate;
+ field public float preferredMinDisplayRefreshRate;
field public int privateFlags;
}
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 12026aa3f72a..4cad58521c09 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -568,13 +568,13 @@ public abstract class Animator implements Cloneable {
* repetition. lastPlayTime is similar and is used to calculate how many repeats have been
* done between the two times.
*/
- void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {}
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime) {}
/**
* Internal use only. This animates any animation that has ended since lastPlayTime.
* If an animation hasn't been finished, no change will be made.
*/
- void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {}
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {}
/**
* Internal use only. Adds all start times (after delay) to and end times to times.
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index f69bbfd3c53f..2198fcd70637 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -111,19 +111,20 @@ public class AnimatorInflater {
float pathErrorScale) throws NotFoundException {
final ConfigurationBoundResourceCache<Animator> animatorCache = resources
.getAnimatorCache();
- Animator animator = animatorCache.getInstance(id, resources, theme);
- if (animator != null) {
+ ConfigurationBoundResourceCache.Entry<Animator> animatorEntry =
+ animatorCache.getInstance(id, resources, theme);
+ if (animatorEntry.hasValue()) {
if (DBG_ANIMATOR_INFLATER) {
Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id));
}
- return animator;
+ return animatorEntry.getValue();
} else if (DBG_ANIMATOR_INFLATER) {
Log.d(TAG, "cache miss for animator " + resources.getResourceName(id));
}
XmlResourceParser parser = null;
try {
parser = resources.getAnimation(id);
- animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale);
+ Animator animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale);
if (animator != null) {
animator.appendChangingConfigurations(getChangingConfigs(resources, id));
final ConstantState<Animator> constantState = animator.createConstantState();
@@ -131,7 +132,7 @@ public class AnimatorInflater {
if (DBG_ANIMATOR_INFLATER) {
Log.d(TAG, "caching animator for res " + resources.getResourceName(id));
}
- animatorCache.put(id, theme, constantState);
+ animatorCache.put(id, theme, constantState, animatorEntry.getGeneration());
// create a new animator so that cached version is never used by the user
animator = constantState.newInstance(resources, theme);
}
@@ -160,20 +161,22 @@ public class AnimatorInflater {
final ConfigurationBoundResourceCache<StateListAnimator> cache = resources
.getStateListAnimatorCache();
final Theme theme = context.getTheme();
- StateListAnimator animator = cache.getInstance(id, resources, theme);
- if (animator != null) {
- return animator;
+ ConfigurationBoundResourceCache.Entry<StateListAnimator> animatorEntry =
+ cache.getInstance(id, resources, theme);
+ if (animatorEntry.hasValue()) {
+ return animatorEntry.getValue();
}
XmlResourceParser parser = null;
try {
parser = resources.getAnimation(id);
- animator = createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
+ StateListAnimator animator =
+ createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
if (animator != null) {
animator.appendChangingConfigurations(getChangingConfigs(resources, id));
final ConstantState<StateListAnimator> constantState = animator
.createConstantState();
if (constantState != null) {
- cache.put(id, theme, constantState);
+ cache.put(id, theme, constantState, animatorEntry.getGeneration());
// return a clone so that the animator in constant state is never used.
animator = constantState.newInstance(resources, theme);
}
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 60659dc12342..70c3d7ae3f82 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -825,8 +825,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
private void animateBasedOnPlayTime(
long currentPlayTime,
long lastPlayTime,
- boolean inReverse,
- boolean notify
+ boolean inReverse
) {
if (currentPlayTime < 0 || lastPlayTime < -1) {
throw new UnsupportedOperationException("Error: Play time should never be negative.");
@@ -857,8 +856,8 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
while (index < endIndex) {
long playTime = startEndTimes[index];
if (lastPlayTime != playTime) {
- animateSkipToEnds(playTime, lastPlayTime, notify);
- animateValuesInRange(playTime, lastPlayTime, notify);
+ animateSkipToEnds(playTime, lastPlayTime);
+ animateValuesInRange(playTime, lastPlayTime);
lastPlayTime = playTime;
}
index++;
@@ -868,15 +867,15 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
index--;
long playTime = startEndTimes[index];
if (lastPlayTime != playTime) {
- animateSkipToEnds(playTime, lastPlayTime, notify);
- animateValuesInRange(playTime, lastPlayTime, notify);
+ animateSkipToEnds(playTime, lastPlayTime);
+ animateValuesInRange(playTime, lastPlayTime);
lastPlayTime = playTime;
}
}
}
if (currentPlayTime != lastPlayTime) {
- animateSkipToEnds(currentPlayTime, lastPlayTime, notify);
- animateValuesInRange(currentPlayTime, lastPlayTime, notify);
+ animateSkipToEnds(currentPlayTime, lastPlayTime);
+ animateValuesInRange(currentPlayTime, lastPlayTime);
}
}
@@ -896,13 +895,11 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
}
@Override
- void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {
initAnimation();
if (lastPlayTime > currentPlayTime) {
- if (notify) {
- notifyStartListeners(true);
- }
+ notifyStartListeners(true);
for (int i = mEvents.size() - 1; i >= 0; i--) {
AnimationEvent event = mEvents.get(i);
Node node = event.mNode;
@@ -916,31 +913,25 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
if (currentPlayTime <= start && start < lastPlayTime) {
animator.animateSkipToEnds(
0,
- lastPlayTime - node.mStartTime,
- notify
+ lastPlayTime - node.mStartTime
);
- if (notify) {
- mPlayingSet.remove(node);
- }
+ mPlayingSet.remove(node);
} else if (start <= currentPlayTime && currentPlayTime <= end) {
animator.animateSkipToEnds(
currentPlayTime - node.mStartTime,
- lastPlayTime - node.mStartTime,
- notify
+ lastPlayTime - node.mStartTime
);
- if (notify && !mPlayingSet.contains(node)) {
+ if (!mPlayingSet.contains(node)) {
mPlayingSet.add(node);
}
}
}
}
- if (currentPlayTime <= 0 && notify) {
+ if (currentPlayTime <= 0) {
notifyEndListeners(true);
}
} else {
- if (notify) {
- notifyStartListeners(false);
- }
+ notifyStartListeners(false);
int eventsSize = mEvents.size();
for (int i = 0; i < eventsSize; i++) {
AnimationEvent event = mEvents.get(i);
@@ -955,45 +946,39 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
if (lastPlayTime < end && end <= currentPlayTime) {
animator.animateSkipToEnds(
end - node.mStartTime,
- lastPlayTime - node.mStartTime,
- notify
+ lastPlayTime - node.mStartTime
);
- if (notify) {
- mPlayingSet.remove(node);
- }
+ mPlayingSet.remove(node);
} else if (start <= currentPlayTime && currentPlayTime <= end) {
animator.animateSkipToEnds(
currentPlayTime - node.mStartTime,
- lastPlayTime - node.mStartTime,
- notify
+ lastPlayTime - node.mStartTime
);
- if (notify && !mPlayingSet.contains(node)) {
+ if (!mPlayingSet.contains(node)) {
mPlayingSet.add(node);
}
}
}
}
- if (currentPlayTime >= getTotalDuration() && notify) {
+ if (currentPlayTime >= getTotalDuration()) {
notifyEndListeners(false);
}
}
}
@Override
- void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime) {
initAnimation();
- if (notify) {
- if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
- notifyStartListeners(false);
- } else {
- long duration = getTotalDuration();
- if (duration >= 0
- && (lastPlayTime > duration || (lastPlayTime == duration
- && currentPlayTime < duration))
- ) {
- notifyStartListeners(true);
- }
+ if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+ notifyStartListeners(false);
+ } else {
+ long duration = getTotalDuration();
+ if (duration >= 0
+ && (lastPlayTime > duration || (lastPlayTime == duration
+ && currentPlayTime < duration))
+ ) {
+ notifyStartListeners(true);
}
}
@@ -1014,8 +999,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
) {
animator.animateValuesInRange(
currentPlayTime - node.mStartTime,
- Math.max(-1, lastPlayTime - node.mStartTime),
- notify
+ Math.max(-1, lastPlayTime - node.mStartTime)
);
}
}
@@ -1111,7 +1095,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
}
}
mSeekState.setPlayTime(playTime, mReversing);
- animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true);
+ animateBasedOnPlayTime(playTime, lastPlayTime, mReversing);
}
/**
@@ -1144,16 +1128,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
private void initChildren() {
if (!isInitialized()) {
mChildrenInitialized = true;
-
- // We have to initialize all the start values so that they are based on the previous
- // values.
- long[] times = ensureChildStartAndEndTimes();
-
- long previousTime = -1;
- for (long time : times) {
- animateBasedOnPlayTime(time, previousTime, false, false);
- previousTime = time;
- }
+ skipToEndValue(false);
}
}
@@ -1489,6 +1464,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
anim.mPauseTime = -1;
anim.mSeekState = new SeekState();
anim.mSelfPulse = true;
+ anim.mStartListenersCalled = false;
anim.mPlayingSet = new ArrayList<Node>();
anim.mNodeMap = new ArrayMap<Animator, Node>();
anim.mNodes = new ArrayList<Node>(nodeCount);
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index ead238f75ba4..5de7f387b206 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1417,21 +1417,19 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
* will be called.
*/
@Override
- void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime) {
if (currentPlayTime < 0 || lastPlayTime < -1) {
throw new UnsupportedOperationException("Error: Play time should never be negative.");
}
initAnimation();
long duration = getTotalDuration();
- if (notify) {
- if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
- notifyStartListeners(false);
- } else if (lastPlayTime > duration
- || (lastPlayTime == duration && currentPlayTime < duration)
- ) {
- notifyStartListeners(true);
- }
+ if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+ notifyStartListeners(false);
+ } else if (lastPlayTime > duration
+ || (lastPlayTime == duration && currentPlayTime < duration)
+ ) {
+ notifyStartListeners(true);
}
if (duration >= 0) {
lastPlayTime = Math.min(duration, lastPlayTime);
@@ -1448,7 +1446,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
iteration = Math.min(iteration, mRepeatCount);
lastIteration = Math.min(lastIteration, mRepeatCount);
- if (notify && iteration != lastIteration) {
+ if (iteration != lastIteration) {
notifyListeners(AnimatorCaller.ON_REPEAT, false);
}
}
@@ -1464,7 +1462,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
}
@Override
- void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {
boolean inReverse = currentPlayTime < lastPlayTime;
boolean doSkip;
if (currentPlayTime <= 0 && lastPlayTime > 0) {
@@ -1474,13 +1472,9 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
doSkip = duration >= 0 && currentPlayTime >= duration && lastPlayTime < duration;
}
if (doSkip) {
- if (notify) {
- notifyStartListeners(inReverse);
- }
+ notifyStartListeners(inReverse);
skipToEndValue(inReverse);
- if (notify) {
- notifyEndListeners(inReverse);
- }
+ notifyEndListeners(inReverse);
}
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 7be00a045403..3487e0b1f3e8 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -827,6 +827,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*/
public static final int PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS = 1 << 4;
+ /**
+ * Whether AbiOverride was used when installing this application.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_EXT_CPU_OVERRIDE = 1 << 5;
+
/** @hide */
@IntDef(flag = true, prefix = { "PRIVATE_FLAG_EXT_" }, value = {
PRIVATE_FLAG_EXT_PROFILEABLE,
@@ -834,6 +840,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE,
PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK,
PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS,
+ PRIVATE_FLAG_EXT_CPU_OVERRIDE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApplicationInfoPrivateFlagsExt {}
diff --git a/core/java/android/content/res/ConfigurationBoundResourceCache.java b/core/java/android/content/res/ConfigurationBoundResourceCache.java
index 5e10a5768358..4da3c18883bd 100644
--- a/core/java/android/content/res/ConfigurationBoundResourceCache.java
+++ b/core/java/android/content/res/ConfigurationBoundResourceCache.java
@@ -37,16 +37,16 @@ public class ConfigurationBoundResourceCache<T> extends ThemedResourceCache<Cons
* @param key a key that uniquely identifies the drawable resource
* @param resources a Resources object from which to create new instances.
* @param theme the theme where the resource will be used
- * @return a new instance of the resource, or {@code null} if not in
+ * @return an Entry wrapping a new instance of the resource, or {@code null} if not in
* the cache
*/
- public T getInstance(long key, Resources resources, Resources.Theme theme) {
- final ConstantState<T> entry = get(key, theme);
- if (entry != null) {
- return entry.newInstance(resources, theme);
+ public Entry<T> getInstance(long key, Resources resources, Resources.Theme theme) {
+ final Entry<ConstantState<T>> e = get(key, theme);
+ if (e.hasValue()) {
+ return new Entry<>(e.getValue().newInstance(resources, theme), e.getGeneration());
}
- return null;
+ return new Entry<>(null, e.getGeneration());
}
@Override
diff --git a/core/java/android/content/res/DrawableCache.java b/core/java/android/content/res/DrawableCache.java
index d0ebe3304065..b51d14a3c472 100644
--- a/core/java/android/content/res/DrawableCache.java
+++ b/core/java/android/content/res/DrawableCache.java
@@ -40,14 +40,32 @@ class DrawableCache extends ThemedResourceCache<Drawable.ConstantState> {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Drawable getInstance(long key, Resources resources, Resources.Theme theme) {
- final Drawable.ConstantState entry = get(key, theme);
- if (entry != null) {
- return entry.newDrawable(resources, theme);
+ final Entry<Drawable.ConstantState> entry = get(key, theme);
+ if (entry.getValue() != null) {
+ return entry.getValue().newDrawable(resources, theme);
}
return null;
}
+ /**
+ * If the resource is cached, creates and returns a new instance of it.
+ *
+ * @param key a key that uniquely identifies the drawable resource
+ * @param resources a Resources object from which to create new instances.
+ * @param theme the theme where the resource will be used
+ * @return an Entry wrapping a a new instance of the resource, or {@code null} if not in
+ * the cache
+ */
+ public Entry<Drawable> getDrawable(long key, Resources resources, Resources.Theme theme) {
+ final Entry<Drawable.ConstantState> e = get(key, theme);
+ if (e.hasValue()) {
+ return new Entry<>(e.getValue().newDrawable(resources, theme), e.getGeneration());
+ }
+
+ return new Entry<>(null, e.getGeneration());
+ }
+
@Override
public boolean shouldInvalidateEntry(Drawable.ConstantState entry, int configChanges) {
return Configuration.needNewResources(configChanges, entry.getChangingConfigurations());
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 3a2863e5636d..25154d5c1623 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -650,15 +650,21 @@ public class ResourcesImpl {
key = (((long) value.assetCookie) << 32) | value.data;
}
+ int cacheGeneration;
// First, check whether we have a cached version of this drawable
// that was inflated against the specified theme. Skip the cache if
// we're currently preloading or we're not using the cache.
if (!mPreloading && useCache) {
- final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
- if (cachedDrawable != null) {
- cachedDrawable.setChangingConfigurations(value.changingConfigurations);
- return cachedDrawable;
+ final ThemedResourceCache.Entry<Drawable> cachedDrawable =
+ caches.getDrawable(key, wrapper, theme);
+ if (cachedDrawable.hasValue()) {
+ cachedDrawable.getValue().setChangingConfigurations(
+ value.changingConfigurations);
+ return cachedDrawable.getValue();
}
+ cacheGeneration = cachedDrawable.getGeneration();
+ } else {
+ cacheGeneration = ThemedResourceCache.UNDEFINED_GENERATION;
}
// Next, check preloaded drawables. Preloaded drawables may contain
@@ -702,7 +708,8 @@ public class ResourcesImpl {
if (dr != null) {
dr.setChangingConfigurations(value.changingConfigurations);
if (useCache) {
- cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
+ cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr,
+ cacheGeneration);
if (needsNewDrawableAfterCache) {
Drawable.ConstantState state = dr.getConstantState();
if (state != null) {
@@ -733,7 +740,7 @@ public class ResourcesImpl {
}
private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
- Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
+ Resources.Theme theme, boolean usesTheme, long key, Drawable dr, int cacheGeneration) {
final Drawable.ConstantState cs = dr.getConstantState();
if (cs == null) {
return;
@@ -761,7 +768,7 @@ public class ResourcesImpl {
}
} else {
synchronized (mAccessLock) {
- caches.put(key, theme, cs, usesTheme);
+ caches.put(key, theme, cs, cacheGeneration, usesTheme);
}
}
}
@@ -1002,14 +1009,16 @@ public class ResourcesImpl {
TypedValue value, int id) {
final long key = (((long) value.assetCookie) << 32) | value.data;
final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache;
- ComplexColor complexColor = cache.getInstance(key, wrapper, theme);
- if (complexColor != null) {
- return complexColor;
+ ThemedResourceCache.Entry<ComplexColor> complexColorEntry =
+ cache.getInstance(key, wrapper, theme);
+ if (complexColorEntry.hasValue()) {
+ return complexColorEntry.getValue();
}
final android.content.res.ConstantState<ComplexColor> factory =
sPreloadedComplexColors.get(key);
+ ComplexColor complexColor = null;
if (factory != null) {
complexColor = factory.newInstance(wrapper, theme);
}
@@ -1026,7 +1035,8 @@ public class ResourcesImpl {
sPreloadedComplexColors.put(key, complexColor.getConstantState());
}
} else {
- cache.put(key, theme, complexColor.getConstantState());
+ cache.put(key, theme, complexColor.getConstantState(),
+ complexColorEntry.getGeneration());
}
}
return complexColor;
diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java
index 3270944ce7e3..e7fd2755a941 100644
--- a/core/java/android/content/res/ThemedResourceCache.java
+++ b/core/java/android/content/res/ThemedResourceCache.java
@@ -33,11 +33,37 @@ import java.lang.ref.WeakReference;
* @param <T> type of data to cache
*/
abstract class ThemedResourceCache<T> {
+ public static final int UNDEFINED_GENERATION = -1;
@UnsupportedAppUsage
private ArrayMap<ThemeKey, LongSparseArray<WeakReference<T>>> mThemedEntries;
private LongSparseArray<WeakReference<T>> mUnthemedEntries;
private LongSparseArray<WeakReference<T>> mNullThemedEntries;
+ private int mGeneration;
+
+ public static class Entry<S> {
+ private S mValue;
+ private int mGeneration;
+
+
+ public S getValue() {
+ return mValue;
+ }
+
+ public boolean hasValue() {
+ return mValue != null;
+ }
+
+ public int getGeneration() {
+ return mGeneration;
+ }
+
+ Entry(S value, int generation) {
+ this.mValue = value;
+ this.mGeneration = generation;
+ }
+ }
+
/**
* Adds a new theme-dependent entry to the cache.
*
@@ -45,9 +71,10 @@ abstract class ThemedResourceCache<T> {
* @param theme the theme against which this entry was inflated, or
* {@code null} if the entry has no theme applied
* @param entry the entry to cache
+ * @param generation The generation of the cache to compare against before storing
*/
- public void put(long key, @Nullable Theme theme, @NonNull T entry) {
- put(key, theme, entry, true);
+ public void put(long key, @Nullable Theme theme, @NonNull T entry, int generation) {
+ put(key, theme, entry, generation, true);
}
/**
@@ -57,10 +84,12 @@ abstract class ThemedResourceCache<T> {
* @param theme the theme against which this entry was inflated, or
* {@code null} if the entry has no theme applied
* @param entry the entry to cache
+ * @param generation The generation of the cache to compare against before storing
* @param usesTheme {@code true} if the entry is affected theme changes,
* {@code false} otherwise
*/
- public void put(long key, @Nullable Theme theme, @NonNull T entry, boolean usesTheme) {
+ public void put(long key, @Nullable Theme theme, @NonNull T entry, int generation,
+ boolean usesTheme) {
if (entry == null) {
return;
}
@@ -72,7 +101,8 @@ abstract class ThemedResourceCache<T> {
} else {
entries = getThemedLocked(theme, true);
}
- if (entries != null) {
+ if (entries != null
+ && ((generation == mGeneration) || (generation == UNDEFINED_GENERATION))) {
entries.put(key, new WeakReference<>(entry));
}
}
@@ -86,7 +116,7 @@ abstract class ThemedResourceCache<T> {
* @return a cached entry, or {@code null} if not in the cache
*/
@Nullable
- public T get(long key, @Nullable Theme theme) {
+ public Entry get(long key, @Nullable Theme theme) {
// The themed (includes null-themed) and unthemed caches are mutually
// exclusive, so we'll give priority to whichever one we think we'll
// hit first. Since most of the framework drawables are themed, that's
@@ -96,7 +126,7 @@ abstract class ThemedResourceCache<T> {
if (themedEntries != null) {
final WeakReference<T> themedEntry = themedEntries.get(key);
if (themedEntry != null) {
- return themedEntry.get();
+ return new Entry(themedEntry.get(), mGeneration);
}
}
@@ -104,12 +134,12 @@ abstract class ThemedResourceCache<T> {
if (unthemedEntries != null) {
final WeakReference<T> unthemedEntry = unthemedEntries.get(key);
if (unthemedEntry != null) {
- return unthemedEntry.get();
+ return new Entry(unthemedEntry.get(), mGeneration);
}
}
}
- return null;
+ return new Entry(null, mGeneration);
}
/**
@@ -121,6 +151,7 @@ abstract class ThemedResourceCache<T> {
@UnsupportedAppUsage
public void onConfigurationChange(@Config int configChanges) {
prune(configChanges);
+ mGeneration++;
}
/**
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index b4533042b4a3..76efce56dcf0 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -53,8 +53,6 @@ import android.view.Surface;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
-import libcore.util.EmptyArray;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -667,7 +665,7 @@ public final class DisplayManager {
} else if (category == null || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) {
return getDisplays(displayIds, Objects::nonNull);
}
- return (Display[]) EmptyArray.OBJECT;
+ return new Display[0];
}
private Display[] getDisplays(int[] displayIds, Predicate<Display> predicate) {
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 931adb55a686..ef274a56e1d3 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -23,6 +23,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.app.Application.ActivityLifecycleCallbacks;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -535,7 +536,11 @@ public final class PrintManager {
return null;
}
try {
- mContext.startIntentSender(intent, null, 0, 0, 0);
+ ActivityOptions activityOptions = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ mContext.startIntentSender(intent, null, 0, 0, 0,
+ activityOptions.toBundle());
return new PrintJob(printJob, this);
} catch (SendIntentException sie) {
Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 7e4e4022f00f..5019b85ca503 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1099,21 +1099,25 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
// TODO: Support a ResultReceiver for IME.
// TODO(b/123718661): Make show() work for multi-session IME.
int typesReady = 0;
+ final boolean imeVisible = mState.isSourceOrDefaultVisible(
+ mImeSourceConsumer.getId(), ime());
for (int type = FIRST; type <= LAST; type = type << 1) {
if ((types & type) == 0) {
continue;
}
@AnimationType final int animationType = getAnimationType(type);
final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
- final boolean isImeAnimation = type == ime();
- if (requestedVisible && animationType == ANIMATION_TYPE_NONE
- || animationType == ANIMATION_TYPE_SHOW) {
+ final boolean isIme = type == ime();
+ var alreadyVisible = requestedVisible && (!isIme || imeVisible)
+ && animationType == ANIMATION_TYPE_NONE;
+ var alreadyAnimatingShow = animationType == ANIMATION_TYPE_SHOW;
+ if (alreadyVisible || alreadyAnimatingShow) {
// no-op: already shown or animating in (because window visibility is
// applied before starting animation).
if (DEBUG) Log.d(TAG, String.format(
"show ignored for type: %d animType: %d requestedVisible: %s",
type, animationType, requestedVisible));
- if (isImeAnimation) {
+ if (isIme) {
ImeTracker.forLogging().onCancelled(statsToken,
ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
@@ -1121,13 +1125,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
if (fromIme && animationType == ANIMATION_TYPE_USER) {
// App is already controlling the IME, don't cancel it.
- if (isImeAnimation) {
+ if (isIme) {
ImeTracker.forLogging().onFailed(
statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
continue;
}
- if (isImeAnimation) {
+ if (isIme) {
ImeTracker.forLogging().onProgress(
statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index 470c2801d838..37b6c77e3c74 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -223,6 +223,10 @@ public class InsetsFrameProvider implements Parcelable {
if (mArbitraryRectangle != null) {
sb.append(", mArbitraryRectangle=").append(mArbitraryRectangle.toShortString());
}
+ if (mMinimalInsetsSizeInDisplayCutoutSafe != null) {
+ sb.append(", mMinimalInsetsSizeInDisplayCutoutSafe=")
+ .append(mMinimalInsetsSizeInDisplayCutoutSafe);
+ }
sb.append("}");
return sb.toString();
}
@@ -248,6 +252,7 @@ public class InsetsFrameProvider implements Parcelable {
mInsetsSize = in.readTypedObject(Insets.CREATOR);
mInsetsSizeOverrides = in.createTypedArray(InsetsSizeOverride.CREATOR);
mArbitraryRectangle = in.readTypedObject(Rect.CREATOR);
+ mMinimalInsetsSizeInDisplayCutoutSafe = in.readTypedObject(Insets.CREATOR);
}
@Override
@@ -258,6 +263,7 @@ public class InsetsFrameProvider implements Parcelable {
out.writeTypedObject(mInsetsSize, flags);
out.writeTypedArray(mInsetsSizeOverrides, flags);
out.writeTypedObject(mArbitraryRectangle, flags);
+ out.writeTypedObject(mMinimalInsetsSizeInDisplayCutoutSafe, flags);
}
public boolean idEquals(InsetsFrameProvider o) {
@@ -276,13 +282,16 @@ public class InsetsFrameProvider implements Parcelable {
return mId == other.mId && mSource == other.mSource && mFlags == other.mFlags
&& Objects.equals(mInsetsSize, other.mInsetsSize)
&& Arrays.equals(mInsetsSizeOverrides, other.mInsetsSizeOverrides)
- && Objects.equals(mArbitraryRectangle, other.mArbitraryRectangle);
+ && Objects.equals(mArbitraryRectangle, other.mArbitraryRectangle)
+ && Objects.equals(mMinimalInsetsSizeInDisplayCutoutSafe,
+ other.mMinimalInsetsSizeInDisplayCutoutSafe);
}
@Override
public int hashCode() {
return Objects.hash(mId, mSource, mFlags, mInsetsSize,
- Arrays.hashCode(mInsetsSizeOverrides), mArbitraryRectangle);
+ Arrays.hashCode(mInsetsSizeOverrides), mArbitraryRectangle,
+ mMinimalInsetsSizeInDisplayCutoutSafe);
}
public static final @NonNull Parcelable.Creator<InsetsFrameProvider> CREATOR =
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index e40f8e78dafc..a91781580ac4 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -435,11 +435,15 @@ public interface WindowManager extends ViewManager {
int TRANSIT_KEYGUARD_GOING_AWAY = 7;
/**
* A window is appearing above a locked keyguard.
+ * @deprecated use {@link #TRANSIT_TO_FRONT} + {@link #TRANSIT_FLAG_KEYGUARD_OCCLUDING} for
+ * keyguard occluding with Shell transition.
* @hide
*/
int TRANSIT_KEYGUARD_OCCLUDE = 8;
/**
* A window is made invisible revealing a locked keyguard.
+ * @deprecated use {@link #TRANSIT_TO_BACK} + {@link #TRANSIT_FLAG_KEYGUARD_UNOCCLUDING} for
+ * keyguard occluding with Shell transition.
* @hide
*/
int TRANSIT_KEYGUARD_UNOCCLUDE = 9;
@@ -562,6 +566,25 @@ public interface WindowManager extends ViewManager {
int TRANSIT_FLAG_INVISIBLE = (1 << 10); // 0x400
/**
+ * Transition flag: Indicates that keyguard will be showing (locked) with this transition,
+ * which is the opposite of {@link #TRANSIT_FLAG_KEYGUARD_GOING_AWAY}.
+ * @hide
+ */
+ int TRANSIT_FLAG_KEYGUARD_APPEARING = (1 << 11); // 0x800
+
+ /**
+ * Transition flag: Indicates that keyguard is becoming hidden by an app
+ * @hide
+ */
+ int TRANSIT_FLAG_KEYGUARD_OCCLUDING = (1 << 12); // 0x1000
+
+ /**
+ * Transition flag: Indicates that keyguard is being revealed after an app was occluding it.
+ * @hide
+ */
+ int TRANSIT_FLAG_KEYGUARD_UNOCCLUDING = (1 << 13); // 0x2000
+
+ /**
* @hide
*/
@IntDef(flag = true, prefix = { "TRANSIT_FLAG_" }, value = {
@@ -576,11 +599,28 @@ public interface WindowManager extends ViewManager {
TRANSIT_FLAG_KEYGUARD_GOING_AWAY,
TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT,
TRANSIT_FLAG_INVISIBLE,
+ TRANSIT_FLAG_KEYGUARD_APPEARING,
+ TRANSIT_FLAG_KEYGUARD_OCCLUDING,
+ TRANSIT_FLAG_KEYGUARD_UNOCCLUDING
})
@Retention(RetentionPolicy.SOURCE)
@interface TransitionFlags {}
/**
+ * Transit flags used to signal keyguard visibility is changing for animations.
+ *
+ * <p>These roughly correspond to CLOSE, OPEN, TO_BACK, and TO_FRONT on a hypothetical Keyguard
+ * container. Since Keyguard isn't a container we can't include it in changes and need to send
+ * this information in its own channel.
+ * @hide
+ */
+ int KEYGUARD_VISIBILITY_TRANSIT_FLAGS =
+ (TRANSIT_FLAG_KEYGUARD_GOING_AWAY
+ | TRANSIT_FLAG_KEYGUARD_APPEARING
+ | TRANSIT_FLAG_KEYGUARD_OCCLUDING
+ | TRANSIT_FLAG_KEYGUARD_UNOCCLUDING);
+
+ /**
* Remove content mode: Indicates remove content mode is currently not defined.
* @hide
*/
@@ -3763,6 +3803,7 @@ public interface WindowManager extends ViewManager {
* This value is ignored if {@link #preferredDisplayModeId} is set.
* @hide
*/
+ @TestApi
public float preferredMinDisplayRefreshRate;
/**
@@ -3771,6 +3812,7 @@ public interface WindowManager extends ViewManager {
* This value is ignored if {@link #preferredDisplayModeId} is set.
* @hide
*/
+ @TestApi
public float preferredMaxDisplayRefreshRate;
/** Indicates whether this window wants the HDR conversion is disabled. */
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 5ad74c8803f4..461b78ce4fc2 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -283,6 +283,9 @@ public class AutofillFeatureFlags {
private static final String DEFAULT_AFAA_NON_AUTOFILLABLE_IME_ACTIONS = "3,4";
private static final boolean DEFAULT_AFAA_SHOULD_ENABLE_AUTOFILL_ON_ALL_VIEW_TYPES = true;
private static final boolean DEFAULT_AFAA_SHOULD_ENABLE_MULTILINE_FILTER = true;
+ private static final boolean
+ DEFAULT_AFAA_SHOULD_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE = true;
+ // END AUTOFILL FOR ALL APPS DEFAULTS
private AutofillFeatureFlags() {};
@@ -414,7 +417,8 @@ public class AutofillFeatureFlags {
public static boolean shouldIncludeAllViewsAutofillTypeNotNoneInAssistStructrue() {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
- DEVICE_CONFIG_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE, false);
+ DEVICE_CONFIG_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE,
+ DEFAULT_AFAA_SHOULD_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE);
}
/**
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 8b04d35734ec..63bf5622fbb8 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -21,6 +21,7 @@ import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Context;
@@ -301,7 +302,8 @@ public final class TextClassification implements Parcelable {
Objects.requireNonNull(intent);
return v -> {
try {
- intent.send();
+ intent.send(ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle());
} catch (PendingIntent.CanceledException e) {
Log.e(LOG_TAG, "Error sending PendingIntent", e);
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index a92ca8a4d3f2..59238b40e0c8 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -99,9 +99,6 @@ public final class TransitionInfo implements Parcelable {
/** The container is the display. */
public static final int FLAG_IS_DISPLAY = 1 << 5;
- /** The container can show on top of lock screen. */
- public static final int FLAG_OCCLUDES_KEYGUARD = 1 << 6;
-
/**
* Only for IS_DISPLAY containers. Is set if the display has system alert windows. This is
* used to prevent seamless rotation.
@@ -175,7 +172,6 @@ public final class TransitionInfo implements Parcelable {
FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT,
FLAG_IS_VOICE_INTERACTION,
FLAG_IS_DISPLAY,
- FLAG_OCCLUDES_KEYGUARD,
FLAG_DISPLAY_HAS_ALERT_WINDOWS,
FLAG_IS_INPUT_METHOD,
FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY,
@@ -457,9 +453,6 @@ public final class TransitionInfo implements Parcelable {
if ((flags & FLAG_IS_DISPLAY) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("IS_DISPLAY");
}
- if ((flags & FLAG_OCCLUDES_KEYGUARD) != 0) {
- sb.append(sb.length() == 0 ? "" : "|").append("OCCLUDES_KEYGUARD");
- }
if ((flags & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("DISPLAY_HAS_ALERT_WINDOWS");
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 421d9983d6b7..632208cdb97c 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -163,19 +163,25 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
// Re-populate the top callback to WM if the removed callback was previously the top one.
if (previousTopCallback == callback) {
// We should call onBackCancelled() when an active callback is removed from dispatcher.
- if (mProgressAnimator.isBackAnimationInProgress()
- && callback instanceof OnBackAnimationCallback) {
- // The ProgressAnimator will handle the new topCallback, so we don't want to call
- // onBackCancelled() on it. We call immediately the callback instead.
- OnBackAnimationCallback animatedCallback = (OnBackAnimationCallback) callback;
- animatedCallback.onBackCancelled();
- Log.d(TAG, "The callback was removed while a back animation was in progress, "
- + "an onBackCancelled() was dispatched.");
- }
+ sendCancelledIfInProgress(callback);
setTopOnBackInvokedCallback(getTopCallback());
}
}
+ private void sendCancelledIfInProgress(@NonNull OnBackInvokedCallback callback) {
+ boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
+ if (isInProgress && callback instanceof OnBackAnimationCallback) {
+ OnBackAnimationCallback animatedCallback = (OnBackAnimationCallback) callback;
+ animatedCallback.onBackCancelled();
+ if (DEBUG) {
+ Log.d(TAG, "sendCancelIfRunning: callback canceled");
+ }
+ } else {
+ Log.w(TAG, "sendCancelIfRunning: isInProgress=" + isInProgress
+ + "callback=" + callback);
+ }
+ }
+
@Override
public void registerSystemOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
registerOnBackInvokedCallbackUnchecked(callback, OnBackInvokedDispatcher.PRIORITY_SYSTEM);
@@ -188,9 +194,20 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
mImeDispatcher = null;
}
if (!mAllCallbacks.isEmpty()) {
+ OnBackInvokedCallback topCallback = getTopCallback();
+ if (topCallback != null) {
+ sendCancelledIfInProgress(topCallback);
+ } else {
+ // Should not be possible
+ Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
+ }
// Clear binder references in WM.
setTopOnBackInvokedCallback(null);
}
+
+ // We should also stop running animations since all callbacks have been removed.
+ // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+ Handler.getMain().post(mProgressAnimator::reset);
mAllCallbacks.clear();
mOnBackInvokedCallbacks.clear();
}
@@ -342,12 +359,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Override
public void onBackInvoked() throws RemoteException {
Handler.getMain().post(() -> {
+ boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
mProgressAnimator.reset();
final OnBackInvokedCallback callback = mCallbackRef.get();
if (callback == null) {
Log.d(TAG, "Trying to call onBackInvoked() on a null callback reference.");
return;
}
+ if (callback instanceof OnBackAnimationCallback && !isInProgress) {
+ Log.w(TAG, "ProgressAnimator was not in progress, skip onBackInvoked().");
+ return;
+ }
callback.onBackInvoked();
});
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 3d95dd341cc0..c9e76009136a 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -81,6 +81,10 @@ public class SystemUiSystemPropertiesFlags {
/** Gating the logging of DND state change events. */
public static final Flag LOG_DND_STATE_EVENTS =
releasedFlag("persist.sysui.notification.log_dnd_state_events");
+
+ /** Gating the holding of WakeLocks until NLSes are told about a new notification. */
+ public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION =
+ devFlag("persist.sysui.notification.wake_lock_for_posting_notification");
}
//// == End of flags. Everything below this line is the implementation. == ////
diff --git a/core/res/res/layout/alert_dialog_button_bar_leanback.xml b/core/res/res/layout/alert_dialog_button_bar_leanback.xml
index ea94af662dcf..466811f6d116 100644
--- a/core/res/res/layout/alert_dialog_button_bar_leanback.xml
+++ b/core/res/res/layout/alert_dialog_button_bar_leanback.xml
@@ -21,13 +21,13 @@
android:layout_height="wrap_content"
android:scrollbarAlwaysDrawVerticalTrack="true"
android:scrollIndicators="top|bottom"
- android:fillViewport="true"
- style="?attr/buttonBarStyle">
+ android:fillViewport="true">
<com.android.internal.widget.ButtonBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layoutDirection="locale"
android:orientation="horizontal"
+ style="?attr/buttonBarStyle"
android:gravity="start">
<Button
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index fb3acbe79114..a5b2b853fddd 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5035,8 +5035,8 @@
<string name="display_rotation_camera_compat_toast_after_rotation">Rotate for a better view</string>
<!-- Text on a toast shown when a camera view is started within the app that may not be able
- to display the camera preview correctly while in split screen. [CHAR LIMIT=NONE] -->
- <string name="display_rotation_camera_compat_toast_in_split_screen">Exit split screen for a better view</string>
+ to display the camera preview correctly while in multi-window. [CHAR LIMIT=NONE] -->
+ <string name="display_rotation_camera_compat_toast_in_multi_window">Open <xliff:g id="name" example="MyApp">%s</xliff:g> in full screen for a better view</string>
<!-- Label for button to confirm chosen date or time [CHAR LIMIT=30] -->
<string name="done_label">Done</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6ab671a70c3c..a8518afd7d3d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2533,7 +2533,7 @@
<java-symbol type="string" name="zen_mode_default_events_name" />
<java-symbol type="string" name="zen_mode_default_every_night_name" />
<java-symbol type="string" name="display_rotation_camera_compat_toast_after_rotation" />
- <java-symbol type="string" name="display_rotation_camera_compat_toast_in_split_screen" />
+ <java-symbol type="string" name="display_rotation_camera_compat_toast_in_multi_window" />
<java-symbol type="array" name="config_system_condition_providers" />
<java-symbol type="string" name="muted_by" />
<java-symbol type="string" name="zen_mode_alarm" />
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
index a7538701807a..0525443ecf82 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import android.util.Property;
import android.view.View;
import androidx.test.annotation.UiThreadTest;
@@ -576,6 +577,42 @@ public class AnimatorSetActivityTest {
});
}
+ @Test
+ public void testInitializeWithoutReadingValues() throws Throwable {
+ // Some consumers crash while reading values before the animator starts
+ Property<int[], Integer> property = new Property<>(Integer.class, "firstValue") {
+ @Override
+ public Integer get(int[] target) {
+ throw new IllegalStateException("Shouldn't be called");
+ }
+
+ @Override
+ public void set(int[] target, Integer value) {
+ target[0] = value;
+ }
+ };
+
+ int[] target1 = new int[1];
+ int[] target2 = new int[1];
+ int[] target3 = new int[1];
+ ObjectAnimator animator1 = ObjectAnimator.ofInt(target1, property, 0, 100);
+ ObjectAnimator animator2 = ObjectAnimator.ofInt(target2, property, 0, 100);
+ ObjectAnimator animator3 = ObjectAnimator.ofInt(target3, property, 0, 100);
+ AnimatorSet set = new AnimatorSet();
+ set.playSequentially(animator1, animator2, animator3);
+
+ mActivityRule.runOnUiThread(() -> {
+ set.setCurrentPlayTime(900);
+ assertEquals(100, target1[0]);
+ assertEquals(100, target2[0]);
+ assertEquals(100, target3[0]);
+ set.setCurrentPlayTime(0);
+ assertEquals(0, target1[0]);
+ assertEquals(0, target2[0]);
+ assertEquals(0, target3[0]);
+ });
+ }
+
/**
* Check that the animator list contains exactly the given animators and nothing else.
*/
diff --git a/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java b/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java
index 4f8b85554f5c..b5f18c26dc97 100644
--- a/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java
+++ b/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java
@@ -45,36 +45,40 @@ public class ConfigurationBoundResourceCacheTest
@SmallTest
public void testGetEmpty() {
final Resources res = getActivity().getResources();
- assertNull(mCache.getInstance(-1, res, null));
+ assertNull(mCache.getInstance(-1, res, null).getValue());
}
@SmallTest
public void testSetGet() {
- mCache.put(1, null, new DummyFloatConstantState(5f));
+ mCache.put(1, null, new DummyFloatConstantState(5f),
+ ThemedResourceCache.UNDEFINED_GENERATION);
final Resources res = getActivity().getResources();
- assertEquals(5f, mCache.getInstance(1, res, null));
- assertNotSame(5f, mCache.getInstance(1, res, null));
- assertEquals(null, mCache.getInstance(1, res, getActivity().getTheme()));
+ assertEquals(5f, mCache.getInstance(1, res, null).getValue());
+ assertNotSame(5f, mCache.getInstance(1, res, null).getValue());
+ assertEquals(false, mCache.getInstance(1, res, getActivity().getTheme()).hasValue());
}
@SmallTest
public void testSetGetThemed() {
- mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f));
+ mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f),
+ ThemedResourceCache.UNDEFINED_GENERATION);
final Resources res = getActivity().getResources();
- assertEquals(null, mCache.getInstance(1, res, null));
- assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme()));
- assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme()));
+ assertEquals(false, mCache.getInstance(1, res, null).hasValue());
+ assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme()).getValue());
+ assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme()).getValue());
}
@SmallTest
public void testMultiThreadPutGet() {
- mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f));
- mCache.put(1, null, new DummyFloatConstantState(10f));
+ mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f),
+ ThemedResourceCache.UNDEFINED_GENERATION);
+ mCache.put(1, null, new DummyFloatConstantState(10f),
+ ThemedResourceCache.UNDEFINED_GENERATION);
final Resources res = getActivity().getResources();
- assertEquals(10f, mCache.getInstance(1, res, null));
- assertNotSame(10f, mCache.getInstance(1, res, null));
- assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme()));
- assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme()));
+ assertEquals(10f, mCache.getInstance(1, res, null).getValue());
+ assertNotSame(10f, mCache.getInstance(1, res, null).getValue());
+ assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme()).getValue());
+ assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme()).getValue());
}
@SmallTest
@@ -86,16 +90,17 @@ public class ConfigurationBoundResourceCacheTest
res.getValue(R.dimen.resource_cache_test_generic, staticValue, true);
float staticDim = TypedValue.complexToDimension(staticValue.data, res.getDisplayMetrics());
mCache.put(key, getActivity().getTheme(),
- new DummyFloatConstantState(staticDim, staticValue.changingConfigurations));
+ new DummyFloatConstantState(staticDim, staticValue.changingConfigurations),
+ ThemedResourceCache.UNDEFINED_GENERATION);
final Configuration cfg = res.getConfiguration();
Configuration newCnf = new Configuration(cfg);
newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ?
Configuration.ORIENTATION_PORTRAIT
: Configuration.ORIENTATION_LANDSCAPE;
int changes = calcConfigChanges(res, newCnf);
- assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme()));
+ assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme()).getValue());
mCache.onConfigurationChange(changes);
- assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme()));
+ assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme()).getValue());
}
@SmallTest
@@ -108,7 +113,8 @@ public class ConfigurationBoundResourceCacheTest
float changingDim = TypedValue.complexToDimension(changingValue.data,
res.getDisplayMetrics());
mCache.put(key, getActivity().getTheme(),
- new DummyFloatConstantState(changingDim, changingValue.changingConfigurations));
+ new DummyFloatConstantState(changingDim, changingValue.changingConfigurations),
+ ThemedResourceCache.UNDEFINED_GENERATION);
final Configuration cfg = res.getConfiguration();
Configuration newCnf = new Configuration(cfg);
@@ -116,9 +122,10 @@ public class ConfigurationBoundResourceCacheTest
Configuration.ORIENTATION_PORTRAIT
: Configuration.ORIENTATION_LANDSCAPE;
int changes = calcConfigChanges(res, newCnf);
- assertEquals(changingDim, mCache.getInstance(key, res, getActivity().getTheme()));
+ assertEquals(changingDim,
+ mCache.getInstance(key, res, getActivity().getTheme()).getValue());
mCache.onConfigurationChange(changes);
- assertNull(mCache.get(key, getActivity().getTheme()));
+ assertNull(mCache.get(key, getActivity().getTheme()).getValue());
}
@SmallTest
@@ -133,9 +140,11 @@ public class ConfigurationBoundResourceCacheTest
float changingDim = TypedValue.complexToDimension(changingValue.data,
res.getDisplayMetrics());
mCache.put(R.dimen.resource_cache_test_generic, getActivity().getTheme(),
- new DummyFloatConstantState(staticDim, staticValue.changingConfigurations));
+ new DummyFloatConstantState(staticDim, staticValue.changingConfigurations),
+ ThemedResourceCache.UNDEFINED_GENERATION);
mCache.put(R.dimen.resource_cache_test_orientation_dependent, getActivity().getTheme(),
- new DummyFloatConstantState(changingDim, changingValue.changingConfigurations));
+ new DummyFloatConstantState(changingDim, changingValue.changingConfigurations),
+ ThemedResourceCache.UNDEFINED_GENERATION);
final Configuration cfg = res.getConfiguration();
Configuration newCnf = new Configuration(cfg);
newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ?
@@ -143,15 +152,15 @@ public class ConfigurationBoundResourceCacheTest
: Configuration.ORIENTATION_LANDSCAPE;
int changes = calcConfigChanges(res, newCnf);
assertEquals(staticDim, mCache.getInstance(R.dimen.resource_cache_test_generic, res,
- getActivity().getTheme()));
+ getActivity().getTheme()).getValue());
assertEquals(changingDim,
mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
- getActivity().getTheme()));
+ getActivity().getTheme()).getValue());
mCache.onConfigurationChange(changes);
assertEquals(staticDim, mCache.getInstance(R.dimen.resource_cache_test_generic, res,
- getActivity().getTheme()));
+ getActivity().getTheme()).getValue());
assertNull(mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
- getActivity().getTheme()));
+ getActivity().getTheme()).getValue());
}
@SmallTest
@@ -173,10 +182,12 @@ public class ConfigurationBoundResourceCacheTest
res.getDisplayMetrics());
final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null;
mCache.put(R.dimen.resource_cache_test_generic, theme,
- new DummyFloatConstantState(staticDim, staticValues[i].changingConfigurations));
+ new DummyFloatConstantState(staticDim, staticValues[i].changingConfigurations),
+ ThemedResourceCache.UNDEFINED_GENERATION);
mCache.put(R.dimen.resource_cache_test_orientation_dependent, theme,
new DummyFloatConstantState(changingDim,
- changingValues[i].changingConfigurations));
+ changingValues[i].changingConfigurations),
+ ThemedResourceCache.UNDEFINED_GENERATION);
}
final Configuration cfg = res.getConfiguration();
Configuration newCnf = new Configuration(cfg);
@@ -187,18 +198,18 @@ public class ConfigurationBoundResourceCacheTest
for (int i = 0; i < 2; i++) {
final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null;
assertEquals(staticDim,
- mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme));
+ mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme).getValue());
assertEquals(changingDim,
mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
- theme));
+ theme).getValue());
}
mCache.onConfigurationChange(changes);
for (int i = 0; i < 2; i++) {
final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null;
assertEquals(staticDim,
- mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme));
+ mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme).getValue());
assertNull(mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
- theme));
+ theme).getValue());
}
}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 2ef2d3a968e0..a6e74d0d6b94 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -340,4 +341,42 @@ public class WindowOnBackInvokedDispatcherTest {
verify(mCallback1).onBackCancelled();
verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull());
}
+
+ @Test
+ public void onBackInvoked_calledAfterOnBackStarted() throws RemoteException {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+ OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
+
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+
+ waitForIdle();
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
+
+ callbackInfo.getCallback().onBackInvoked();
+
+ waitForIdle();
+ verify(mCallback1).onBackInvoked();
+ verify(mCallback1, never()).onBackCancelled();
+ }
+
+ @Test
+ public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+
+ OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
+
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+
+ waitForIdle();
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
+
+ // This should trigger mCallback1.onBackCancelled()
+ mDispatcher.detachFromWindow();
+ // This should be ignored by mCallback1
+ callbackInfo.getCallback().onBackInvoked();
+
+ waitForIdle();
+ verify(mCallback1, never()).onBackInvoked();
+ verify(mCallback1).onBackCancelled();
+ }
}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index e4defcfa359f..93e44f1d2f87 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1813,6 +1813,12 @@
"group": "WM_DEBUG_KEEP_SCREEN_ON",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-479665533": {
+ "message": "DisplayRotationCompatPolicy: Multi-window toast not shown as package '%s' cannot be found.",
+ "level": "ERROR",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"-464564167": {
"message": "Current transition prevents automatic focus change",
"level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 513638eeb960..cef7e1666331 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -17,31 +17,25 @@
package com.android.wm.shell.keyguard;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_SLEEP;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
-import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+import static android.view.WindowManager.TRANSIT_SLEEP;
import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
-import static com.android.wm.shell.util.TransitionUtil.isClosingType;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.RemoteException;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.TransitionInfo;
@@ -56,8 +50,6 @@ import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
-import java.util.Map;
-
/**
* The handler for Keyguard enter/exit and occlude/unocclude animations.
*
@@ -70,7 +62,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
private final Handler mMainHandler;
private final ShellExecutor mMainExecutor;
- private final Map<IBinder, IRemoteTransition> mStartedTransitions = new ArrayMap<>();
+ private final ArrayMap<IBinder, StartedTransition> mStartedTransitions = new ArrayMap<>();
/**
* Local IRemoteTransition implementations registered by the keyguard service.
@@ -81,6 +73,18 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
private IRemoteTransition mOccludeByDreamTransition = null;
private IRemoteTransition mUnoccludeTransition = null;
+ private final class StartedTransition {
+ final TransitionInfo mInfo;
+ final SurfaceControl.Transaction mFinishT;
+ final IRemoteTransition mPlayer;
+
+ public StartedTransition(TransitionInfo info,
+ SurfaceControl.Transaction finishT, IRemoteTransition player) {
+ mInfo = info;
+ mFinishT = finishT;
+ mPlayer = player;
+ }
+ }
public KeyguardTransitionHandler(
@NonNull ShellInit shellInit,
@NonNull Transitions transitions,
@@ -105,10 +109,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
}
public static boolean handles(TransitionInfo info) {
- return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0
- || (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0
- || info.getType() == TRANSIT_KEYGUARD_OCCLUDE
- || info.getType() == TRANSIT_KEYGUARD_UNOCCLUDE;
+ return (info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0;
}
@Override
@@ -120,39 +121,14 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
return false;
}
- boolean hasOpeningOcclude = false;
- boolean hasClosingOcclude = false;
- boolean hasOpeningDream = false;
- boolean hasClosingApp = false;
-
- // Check for occluding/dream/closing apps
- for (int i = info.getChanges().size() - 1; i >= 0; i--) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
- continue;
- } else if (isOpeningType(change.getMode())) {
- hasOpeningOcclude |= change.hasFlags(FLAG_OCCLUDES_KEYGUARD);
- hasOpeningDream |= (change.getTaskInfo() != null
- && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_DREAM);
- } else if (isClosingType(change.getMode())) {
- hasClosingOcclude |= change.hasFlags(FLAG_OCCLUDES_KEYGUARD);
- hasClosingApp = true;
- }
- }
-
// Choose a transition applicable for the changes and keyguard state.
if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
return startAnimation(mExitTransition,
"going-away",
transition, info, startTransaction, finishTransaction, finishCallback);
}
- if (hasOpeningOcclude || info.getType() == TRANSIT_KEYGUARD_OCCLUDE) {
- if (hasClosingOcclude) {
- // Transitions between apps on top of the keyguard can use the default handler.
- // WM sends a final occlude status update after the transition is finished.
- return false;
- }
- if (hasOpeningDream) {
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0) {
+ if (hasOpeningDream(info)) {
return startAnimation(mOccludeByDreamTransition,
"occlude-by-dream",
transition, info, startTransaction, finishTransaction, finishCallback);
@@ -161,12 +137,12 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
"occlude",
transition, info, startTransaction, finishTransaction, finishCallback);
}
- } else if (hasClosingApp || info.getType() == TRANSIT_KEYGUARD_UNOCCLUDE) {
+ } else if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
return startAnimation(mUnoccludeTransition,
"unocclude",
transition, info, startTransaction, finishTransaction, finishCallback);
} else {
- Log.w(TAG, "Failed to play: " + info);
+ Log.i(TAG, "Refused to play keyguard transition: " + info);
return false;
}
}
@@ -194,7 +170,8 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
});
}
});
- mStartedTransitions.put(transition, remoteHandler);
+ mStartedTransitions.put(transition,
+ new StartedTransition(info, finishTransaction, remoteHandler));
} catch (RemoteException e) {
Log.wtf(TAG, "RemoteException thrown from local IRemoteTransition", e);
return false;
@@ -207,20 +184,35 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
public void mergeAnimation(@NonNull IBinder nextTransition, @NonNull TransitionInfo nextInfo,
@NonNull SurfaceControl.Transaction nextT, @NonNull IBinder currentTransition,
@NonNull TransitionFinishCallback nextFinishCallback) {
- final IRemoteTransition playing = mStartedTransitions.get(currentTransition);
-
+ final StartedTransition playing = mStartedTransitions.get(currentTransition);
if (playing == null) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"unknown keyguard transition %s", currentTransition);
return;
}
-
- if (nextInfo.getType() == TRANSIT_SLEEP) {
+ if ((nextInfo.getFlags() & WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING) != 0
+ && (playing.mInfo.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
+ // Keyguard unlocking has been canceled. Merge the unlock and re-lock transitions to
+ // avoid a flicker where we flash one frame with the screen fully unlocked.
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "canceling keyguard exit transition %s", currentTransition);
+ playing.mFinishT.merge(nextT);
+ try {
+ playing.mPlayer.mergeAnimation(nextTransition, nextInfo, nextT, currentTransition,
+ new FakeFinishCallback());
+ } catch (RemoteException e) {
+ // There is no good reason for this to happen because the player is a local object
+ // implementing an AIDL interface.
+ Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e);
+ }
+ nextFinishCallback.onTransitionFinished(null, null);
+ } else if (nextInfo.getType() == TRANSIT_SLEEP) {
// An empty SLEEP transition comes in as a signal to abort transitions whenever a sleep
// token is held. In cases where keyguard is showing, we are running the animation for
// the device sleeping/waking, so it's best to ignore this and keep playing anyway.
return;
- } else {
+ } else if (handles(nextInfo)) {
+ // In all other cases, fast-forward to let the next queued transition start playing.
finishAnimationImmediately(currentTransition, playing);
}
}
@@ -228,7 +220,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
@Override
public void onTransitionConsumed(IBinder transition, boolean aborted,
SurfaceControl.Transaction finishTransaction) {
- final IRemoteTransition playing = mStartedTransitions.remove(transition);
+ final StartedTransition playing = mStartedTransitions.remove(transition);
if (playing != null) {
finishAnimationImmediately(transition, playing);
}
@@ -241,13 +233,26 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
return null;
}
- private void finishAnimationImmediately(IBinder transition, IRemoteTransition playing) {
+ private static boolean hasOpeningDream(@NonNull TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; i--) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (isOpeningType(change.getMode())
+ && change.getTaskInfo() != null
+ && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_DREAM) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void finishAnimationImmediately(IBinder transition, StartedTransition playing) {
final IBinder fakeTransition = new Binder();
final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0);
final SurfaceControl.Transaction fakeT = new SurfaceControl.Transaction();
final FakeFinishCallback fakeFinishCb = new FakeFinishCallback();
try {
- playing.mergeAnimation(fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb);
+ playing.mPlayer.mergeAnimation(
+ fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb);
} catch (RemoteException e) {
// There is no good reason for this to happen because the player is a local object
// implementing an AIDL interface.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 281cae5e4ffa..abe2db094a5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -70,6 +70,7 @@ public class PipResizeGestureHandler {
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final PipMotionHelper mMotionHelper;
private final PipBoundsState mPipBoundsState;
+ private final PipTouchState mPipTouchState;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PhonePipMenuController mPhonePipMenuController;
private final PipDismissTargetHandler mPipDismissTargetHandler;
@@ -104,7 +105,6 @@ public class PipResizeGestureHandler {
private boolean mAllowGesture;
private boolean mIsAttached;
private boolean mIsEnabled;
- private boolean mEnableTouch;
private boolean mEnablePinchResize;
private boolean mEnableDragCornerResize;
private boolean mIsSysUiStateValid;
@@ -122,7 +122,8 @@ public class PipResizeGestureHandler {
public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
- PipTaskOrganizer pipTaskOrganizer, PipDismissTargetHandler pipDismissTargetHandler,
+ PipTouchState pipTouchState, PipTaskOrganizer pipTaskOrganizer,
+ PipDismissTargetHandler pipDismissTargetHandler,
Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable,
PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
ShellExecutor mainExecutor) {
@@ -132,6 +133,7 @@ public class PipResizeGestureHandler {
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipBoundsState = pipBoundsState;
mMotionHelper = motionHelper;
+ mPipTouchState = pipTouchState;
mPipTaskOrganizer = pipTaskOrganizer;
mPipDismissTargetHandler = pipDismissTargetHandler;
mMovementBoundsSupplier = movementBoundsSupplier;
@@ -139,7 +141,6 @@ public class PipResizeGestureHandler {
mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
- mEnableTouch = true;
mUpdateResizeBoundsCallback = (rect) -> {
mUserResizeBounds.set(rect);
@@ -250,8 +251,8 @@ public class PipResizeGestureHandler {
return;
}
- if (!mEnableTouch) {
- // No need to handle anything if touches are not enabled for resizing.
+ if (!mPipTouchState.getAllowInputEvents()) {
+ // No need to handle anything if touches are not enabled
return;
}
@@ -588,13 +589,13 @@ public class PipResizeGestureHandler {
mLastResizeBounds, movementBounds);
mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
- // disable the resizing until the final bounds are updated
- mEnableTouch = false;
+ // disable any touch events beyond resizing too
+ mPipTouchState.setAllowInputEvents(false);
mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
- // reset the pinch resizing to its default state
- mEnableTouch = true;
+ // enable touch events
+ mPipTouchState.setAllowInputEvents(true);
});
} else {
mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
index 82fe38ccc7b3..7971c049ff3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -135,14 +135,14 @@ public class PipSizeSpecHandler {
maxWidth = (int) (mOptimizedAspectRatio * shorterLength
+ shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1
+ aspectRatio));
- maxHeight = (int) (maxWidth / aspectRatio);
+ maxHeight = Math.round(maxWidth / aspectRatio);
} else {
if (aspectRatio > 1f) {
maxWidth = shorterLength;
- maxHeight = (int) (maxWidth / aspectRatio);
+ maxHeight = Math.round(maxWidth / aspectRatio);
} else {
maxHeight = shorterLength;
- maxWidth = (int) (maxHeight * aspectRatio);
+ maxWidth = Math.round(maxHeight * aspectRatio);
}
}
@@ -165,10 +165,9 @@ public class PipSizeSpecHandler {
Size maxSize = this.getMaxSize(aspectRatio);
- int defaultWidth = Math.max((int) (maxSize.getWidth() * mDefaultSizePercent),
+ int defaultWidth = Math.max(Math.round(maxSize.getWidth() * mDefaultSizePercent),
minSize.getWidth());
- int defaultHeight = Math.max((int) (maxSize.getHeight() * mDefaultSizePercent),
- minSize.getHeight());
+ int defaultHeight = Math.round(defaultWidth / aspectRatio);
return new Size(defaultWidth, defaultHeight);
}
@@ -188,16 +187,16 @@ public class PipSizeSpecHandler {
Size maxSize = this.getMaxSize(aspectRatio);
- int minWidth = (int) (maxSize.getWidth() * mMinimumSizePercent);
- int minHeight = (int) (maxSize.getHeight() * mMinimumSizePercent);
+ int minWidth = Math.round(maxSize.getWidth() * mMinimumSizePercent);
+ int minHeight = Math.round(maxSize.getHeight() * mMinimumSizePercent);
// make sure the calculated min size is not smaller than the allowed default min size
if (aspectRatio > 1f) {
- minHeight = (int) Math.max(minHeight, mDefaultMinSize);
- minWidth = (int) (minHeight * aspectRatio);
+ minHeight = Math.max(minHeight, mDefaultMinSize);
+ minWidth = Math.round(minHeight * aspectRatio);
} else {
- minWidth = (int) Math.max(minWidth, mDefaultMinSize);
- minHeight = (int) (minWidth / aspectRatio);
+ minWidth = Math.max(minWidth, mDefaultMinSize);
+ minHeight = Math.round(minWidth / aspectRatio);
}
return new Size(minWidth, minHeight);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 466da0e85358..2c4f76b1f34b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -199,11 +199,6 @@ public class PipTouchHandler {
mMotionHelper = pipMotionHelper;
mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
mMotionHelper, mainExecutor);
- mPipResizeGestureHandler =
- new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
- mMotionHelper, pipTaskOrganizer, mPipDismissTargetHandler,
- this::getMovementBounds, this::updateMovementBounds, pipUiEventLogger,
- menuController, mainExecutor);
mTouchState = new PipTouchState(ViewConfiguration.get(context),
() -> {
if (mPipBoundsState.isStashed()) {
@@ -220,6 +215,11 @@ public class PipTouchHandler {
},
menuController::hideMenu,
mainExecutor);
+ mPipResizeGestureHandler =
+ new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
+ mMotionHelper, mTouchState, pipTaskOrganizer, mPipDismissTargetHandler,
+ this::getMovementBounds, this::updateMovementBounds, pipUiEventLogger,
+ menuController, mainExecutor);
mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState,
mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
this::onAccessibilityShowMenu, this::updateMovementBounds,
@@ -556,6 +556,11 @@ public class PipTouchHandler {
return true;
}
+ // do not process input event if not allowed
+ if (!mTouchState.getAllowInputEvents()) {
+ return true;
+ }
+
MotionEvent ev = (MotionEvent) inputEvent;
if (!mPipBoundsState.isStashed() && mPipResizeGestureHandler.willStartResizeGesture(ev)) {
// Initialize the touch state for the gesture, but immediately reset to invalidate the
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
index d7d69f27f9f8..7f62c629c7f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
@@ -37,7 +37,7 @@ public class PipTouchState {
private static final boolean DEBUG = false;
@VisibleForTesting
- public static final long DOUBLE_TAP_TIMEOUT = 200;
+ public static final long DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
static final long HOVER_EXIT_TIMEOUT = 50;
private final ShellExecutor mMainExecutor;
@@ -55,6 +55,9 @@ public class PipTouchState {
private final PointF mLastDelta = new PointF();
private final PointF mVelocity = new PointF();
private boolean mAllowTouches = true;
+
+ // Set to false to block both PipTouchHandler and PipResizeGestureHandler's input processing
+ private boolean mAllowInputEvents = true;
private boolean mIsUserInteracting = false;
// Set to true only if the multiple taps occur within the double tap timeout
private boolean mIsDoubleTap = false;
@@ -77,6 +80,20 @@ public class PipTouchState {
}
/**
+ * @return true if input processing is enabled for PiP in general.
+ */
+ public boolean getAllowInputEvents() {
+ return mAllowInputEvents;
+ }
+
+ /**
+ * @param allowInputEvents true to enable input processing for PiP in general.
+ */
+ public void setAllowInputEvents(boolean allowInputEvents) {
+ mAllowInputEvents = allowInputEvents;
+ }
+
+ /**
* Resets this state.
*/
public void reset() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 366f4f143374..e616965a29ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -24,7 +24,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -580,6 +579,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRecentTasks.get().removeSplitPair(taskId1);
}
options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, null);
wct.startTask(taskId1, options1);
mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
return;
@@ -601,6 +601,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId == INVALID_TASK_ID) {
options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, null);
wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
return;
@@ -621,6 +622,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId == INVALID_TASK_ID) {
options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, null);
wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
return;
@@ -679,6 +681,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (pendingIntent2 == null) {
options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, null);
if (shortcutInfo1 != null) {
wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
} else {
@@ -2407,7 +2410,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (change.getMode() == TRANSIT_CHANGE
&& (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
mSplitLayout.update(startTransaction);
- record.mContainDisplayChange = true;
}
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -2439,8 +2441,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final StageTaskListener stage = getStageOfTask(taskInfo);
if (stage == null) {
if (change.getParent() == null && !isClosingType(change.getMode())
- && taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
- record.mContainShowPipChange = true;
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ record.mContainShowFullscreenChange = true;
}
continue;
}
@@ -2459,15 +2461,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
final ArraySet<StageTaskListener> dismissStages = record.getShouldDismissedStage();
- if (!record.mContainDisplayChange && record.mContainShowPipChange) {
- // This occurred when split enter pip by open another fullscreen app so let
- // pip tranistion handler to handle this but also start our dismiss transition.
- // TODO(b/282894249): Should improve this case animation on pip.
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
- mSplitTransitions.startDismissTransition(wct, this, STAGE_TYPE_UNDEFINED,
- EXIT_REASON_CHILD_TASK_ENTER_PIP);
- } else if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0
|| dismissStages.size() == 1) {
// If the size of dismissStages == 1, one of the task is closed without prepare
// pending transition, which could happen if all activities were finished after
@@ -2480,9 +2474,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final int dismissTop = (dismissStages.size() == 1
&& getStageType(dismissStages.valueAt(0)) == STAGE_TYPE_MAIN)
|| mMainStage.getChildCount() == 0 ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
- prepareExitSplitScreen(dismissTop, wct);
+ // If there is a fullscreen opening change, we should not bring stage to top.
+ prepareExitSplitScreen(record.mContainShowFullscreenChange
+ ? STAGE_TYPE_UNDEFINED : dismissTop, wct);
mSplitTransitions.startDismissTransition(wct, this, dismissTop,
- EXIT_REASON_UNKNOWN);
+ EXIT_REASON_APP_FINISHED);
// This can happen in some pathological cases. For example:
// 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C]
// 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time
@@ -2506,8 +2502,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
static class StageChangeRecord {
- boolean mContainDisplayChange = false;
- boolean mContainShowPipChange = false;
+ boolean mContainShowFullscreenChange = false;
static class StageChange {
final StageTaskListener mStageTaskListener;
final IntArray mAddedTaskId = new IntArray();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 64571e067a83..3ec433e55da9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -31,6 +31,7 @@ import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
+import android.util.Log;
import android.util.Pair;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -566,11 +567,18 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- boolean consumed = mKeyguardHandler.startAnimation(
- mixed.mTransition, info, startTransaction, finishTransaction, finishCallback);
- if (!consumed) {
+ final Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ mixed.mInFlightSubAnimations--;
+ if (mixed.mInFlightSubAnimations == 0) {
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(wct, wctCB);
+ }
+ };
+ if (!mKeyguardHandler.startAnimation(
+ mixed.mTransition, info, startTransaction, finishTransaction, finishCB)) {
return false;
}
+ mixed.mInFlightSubAnimations++;
// Sync pip state.
if (mPipHandler != null) {
// We don't know when to apply `startTransaction` so use a separate transaction here.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index a4ac261d1946..ef7bedf49a92 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
@@ -55,20 +54,24 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
- /** Defines the transition used to run the test */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- pipApp.launchViaIntent(wmHelper)
- pipApp.enableAutoEnterForPipActivity()
- }
- teardown {
- // close gracefully so that onActivityUnpinned() can be called before force exit
- pipApp.closePipWindow(wmHelper)
- pipApp.exit(wmHelper)
- }
- transitions { tapl.goHome() }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { tapl.goHome() }
+ }
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ pipApp.launchViaIntent(wmHelper)
+ pipApp.enableAutoEnterForPipActivity()
}
+ }
+
+ override val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown {
+ // close gracefully so that onActivityUnpinned() can be called before force exit
+ pipApp.closePipWindow(wmHelper)
+ pipApp.exit(wmHelper)
+ }
+ }
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index 98fc91b334cf..afcc1729ed16 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -54,40 +54,38 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class ClosePipBySwipingDownTest(flicker: FlickerTest) : ClosePipTransition(flicker) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- val pipRegion = wmHelper.getWindowRegion(pipApp).bounds
- val pipCenterX = pipRegion.centerX()
- val pipCenterY = pipRegion.centerY()
- val displayCenterX = device.displayWidth / 2
- val barComponent =
- if (flicker.scenario.isTablet) {
- ComponentNameMatcher.TASK_BAR
- } else {
- ComponentNameMatcher.NAV_BAR
- }
- val barLayerHeight =
- wmHelper.currentState.layerState
- .getLayerWithBuffer(barComponent)
- ?.visibleRegion
- ?.height
- ?: error("Couldn't find Nav or Task bar layer")
- // The dismiss button doesn't appear at the complete bottom of the screen,
- // it appears above the hot seat but `hotseatBarSize` is not available outside
- // the platform
- val displayY = (device.displayHeight * 0.9).toInt() - barLayerHeight
- device.swipe(pipCenterX, pipCenterY, displayCenterX, displayY, 50)
- // Wait until the other app is no longer visible
- wmHelper
- .StateSyncBuilder()
- .withPipGone()
- .withWindowSurfaceDisappeared(pipApp)
- .withAppTransitionIdle()
- .waitForAndVerify()
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions {
+ val pipRegion = wmHelper.getWindowRegion(pipApp).bounds
+ val pipCenterX = pipRegion.centerX()
+ val pipCenterY = pipRegion.centerY()
+ val displayCenterX = device.displayWidth / 2
+ val barComponent =
+ if (flicker.scenario.isTablet) {
+ ComponentNameMatcher.TASK_BAR
+ } else {
+ ComponentNameMatcher.NAV_BAR
+ }
+ val barLayerHeight =
+ wmHelper.currentState.layerState
+ .getLayerWithBuffer(barComponent)
+ ?.visibleRegion
+ ?.height
+ ?: error("Couldn't find Nav or Task bar layer")
+ // The dismiss button doesn't appear at the complete bottom of the screen,
+ // it appears above the hot seat but `hotseatBarSize` is not available outside
+ // the platform
+ val displayY = (device.displayHeight * 0.9).toInt() - barLayerHeight
+ device.swipe(pipCenterX, pipCenterY, displayCenterX, displayY, 50)
+ // Wait until the other app is no longer visible
+ wmHelper
+ .StateSyncBuilder()
+ .withPipGone()
+ .withWindowSurfaceDisappeared(pipApp)
+ .withAppTransitionIdle()
+ .waitForAndVerify()
}
+ }
/** Checks that the focus doesn't change between windows during the transition */
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
index 2417c45bf9a0..e52b71e602f9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
@@ -28,11 +28,10 @@ import org.junit.runners.Parameterized
/** Base class for exiting pip (closing pip window) without returning to the app */
abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker) {
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- setup { this.setRotation(flicker.scenario.startRotation) }
- teardown { this.setRotation(Rotation.ROTATION_0) }
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup { this.setRotation(flicker.scenario.startRotation) }
+ teardown { this.setRotation(Rotation.ROTATION_0) }
+ }
/**
* Checks that [pipApp] window is pinned and visible at the start and then becomes unpinned and
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index d16583271e8c..86fe583c94e6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -54,12 +54,9 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class ClosePipWithDismissButtonTest(flicker: FlickerTest) : ClosePipTransition(flicker) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions { pipApp.closePipWindow(wmHelper) }
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { pipApp.closePipWindow(wmHelper) }
+ }
/**
* Checks that the focus changes between the pip menu window and the launcher when clicking the
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index f52e877ec2b1..01d67cc35a14 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -45,20 +45,24 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTransition(flicker) {
- /** Defines the transition used to run the test */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- pipApp.launchViaIntent(wmHelper)
- pipApp.enableEnterPipOnUserLeaveHint()
- }
- teardown {
- // close gracefully so that onActivityUnpinned() can be called before force exit
- pipApp.closePipWindow(wmHelper)
- pipApp.exit(wmHelper)
- }
- transitions { tapl.goHome() }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { tapl.goHome() }
+ }
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ pipApp.launchViaIntent(wmHelper)
+ pipApp.enableEnterPipOnUserLeaveHint()
+ }
+ }
+
+ override val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown {
+ // close gracefully so that onActivityUnpinned() can be called before force exit
+ pipApp.closePipWindow(wmHelper)
+ pipApp.exit(wmHelper)
}
+ }
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index 4b4613704a16..5480144ba1ce 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -73,39 +73,39 @@ open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flic
private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
- /** Defines the transition used to run the test */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- // Launch a portrait only app on the fullscreen stack
- testApp.launchViaIntent(
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ teardown {
+ testApp.exit(wmHelper)
+ }
+ transitions {
+ // Enter PiP, and assert that the PiP is within bounds now that the device is back
+ // in portrait
+ broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
+ // during rotation the status bar becomes invisible and reappears at the end
+ wmHelper
+ .StateSyncBuilder()
+ .withPipShown()
+ .withNavOrTaskBarVisible()
+ .withStatusBarVisible()
+ .waitForAndVerify()
+ }
+ }
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ // Launch a portrait only app on the fullscreen stack
+ testApp.launchViaIntent(
wmHelper,
stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())
- )
- // Launch the PiP activity fixed as landscape
- pipApp.launchViaIntent(
+ )
+ // Launch the PiP activity fixed as landscape, but don't enter PiP
+ pipApp.launchViaIntent(
wmHelper,
stringExtras =
- mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
- )
- }
- teardown {
- pipApp.exit(wmHelper)
- testApp.exit(wmHelper)
- }
- transitions {
- // Enter PiP, and assert that the PiP is within bounds now that the device is back
- // in portrait
- broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
- // during rotation the status bar becomes invisible and reappears at the end
- wmHelper
- .StateSyncBuilder()
- .withPipShown()
- .withNavOrTaskBarVisible()
- .withStatusBarVisible()
- .waitForAndVerify()
- }
+ mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+ )
}
+ }
/**
* This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
index bfd57786e615..95121def07bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
@@ -26,12 +26,11 @@ import org.junit.Test
import org.junit.runners.Parameterized
abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) {
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup { pipApp.launchViaIntent(wmHelper) }
- teardown { pipApp.exit(wmHelper) }
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ pipApp.launchViaIntent(wmHelper)
}
+ }
/** Checks [pipApp] window remains visible throughout the animation */
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index f1925d8c9d85..95725b64a48a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -51,11 +51,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class EnterPipViaAppUiButtonTest(flicker: FlickerTest) : EnterPipTransition(flicker) {
-
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions { pipApp.clickEnterPipButton(wmHelper) }
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { pipApp.clickEnterPipButton(wmHelper) }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 3e0e37dfc997..0b3d16a8087d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -53,19 +53,16 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class ExitPipToAppViaExpandButtonTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
-
- /** Defines the transition used to run the test */
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- setup {
- // launch an app behind the pip one
- testApp.launchViaIntent(wmHelper)
- }
- transitions {
- // This will bring PipApp to fullscreen
- pipApp.expandPipWindowToApp(wmHelper)
- // Wait until the other app is no longer visible
- wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ // launch an app behind the pip one
+ testApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ // This will bring PipApp to fullscreen
+ pipApp.expandPipWindowToApp(wmHelper)
+ // Wait until the other app is no longer visible
+ wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
}
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index 603f99541a12..bb2d40becdc9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -52,19 +52,16 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class ExitPipToAppViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
-
- /** Defines the transition used to run the test */
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- setup {
- // launch an app behind the pip one
- testApp.launchViaIntent(wmHelper)
- }
- transitions {
- // This will bring PipApp to fullscreen
- pipApp.exitPipToFullScreenViaIntent(wmHelper)
- // Wait until the other app is no longer visible
- wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ // launch an app behind the pip one
+ testApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ // This will bring PipApp to fullscreen
+ pipApp.exitPipToFullScreenViaIntent(wmHelper)
+ // Wait until the other app is no longer visible
+ wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
}
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index bbb1c6c2ac63..fd16b6ea6ada 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -56,8 +56,9 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) {
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition { transitions { pipApp.doubleClickPipWindow(wmHelper) } }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { pipApp.doubleClickPipWindow(wmHelper) }
+ }
/**
* Checks that the pip app window remains inside the display bounds throughout the whole
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index d860e00fbfff..253aa4cae5c7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -35,8 +35,9 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) {
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) } }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) }
+ }
/** Checks that the visible region area of [pipApp] always increases during the animation. */
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index d8d57d219933..094060f86691 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -56,15 +56,10 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class MovePipDownOnShelfHeightChange(flicker: FlickerTest) : MovePipShelfHeightTransition(flicker) {
- /** Defines the transition used to run the test */
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- teardown {
- tapl.pressHome()
- testApp.exit(wmHelper)
- }
- transitions { testApp.launchViaIntent(wmHelper) }
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ teardown { testApp.exit(wmHelper) }
+ transitions { testApp.launchViaIntent(wmHelper) }
+ }
/** Checks that the visible region of [pipApp] window always moves down during the animation. */
@Presubmit @Test fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index 6b061bbb1565..ff51c27bf116 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -41,23 +41,21 @@ import org.junit.runners.Parameterized
open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransition(flicker) {
private val imeApp = ImeAppHelper(instrumentation)
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- setup {
- imeApp.launchViaIntent(wmHelper)
- setRotation(flicker.scenario.startRotation)
- }
- teardown { imeApp.exit(wmHelper) }
- transitions {
- // open the soft keyboard
- imeApp.openIME(wmHelper)
- createTag(TAG_IME_VISIBLE)
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ imeApp.launchViaIntent(wmHelper)
+ setRotation(flicker.scenario.startRotation)
+ }
+ teardown { imeApp.exit(wmHelper) }
+ transitions {
+ // open the soft keyboard
+ imeApp.openIME(wmHelper)
+ createTag(TAG_IME_VISIBLE)
- // then close it again
- imeApp.closeIME(wmHelper)
- }
+ // then close it again
+ imeApp.closeIME(wmHelper)
}
+ }
/** Ensure the pip window remains visible throughout any keyboard interactions */
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index ae3f87967658..27b061b67a85 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -57,14 +57,12 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class MovePipUpOnShelfHeightChangeTest(flicker: FlickerTest) :
MovePipShelfHeightTransition(flicker) {
- /** Defines the transition used to run the test */
- override val transition: FlickerBuilder.() -> Unit
- get() =
- buildTransition() {
- setup { testApp.launchViaIntent(wmHelper) }
- transitions { tapl.pressHome() }
- teardown { testApp.exit(wmHelper) }
- }
+ override val thisTransition: FlickerBuilder.() -> Unit =
+ {
+ setup { testApp.launchViaIntent(wmHelper) }
+ transitions { tapl.pressHome() }
+ teardown { testApp.exit(wmHelper) }
+ }
/** Checks that the visible region of [pipApp] window always moves up during the animation. */
@Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index 4e2a4e700698..9f81ba8eee87 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -22,7 +22,6 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
import com.android.server.wm.flicker.testapp.ActivityOptions
import org.junit.FixMethodOrder
import org.junit.Test
@@ -37,28 +36,31 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) {
private var isDraggedLeft: Boolean = true
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- val stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
- setup {
- tapl.setEnableRotation(true)
- // Launch the PIP activity and wait for it to enter PiP mode
- RemoveAllTasksButHomeRule.removeAllTasksButHome()
- pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { pipApp.dragPipWindowAwayFromEdgeWithoutRelease(wmHelper, 50) }
+ }
- // determine the direction of dragging to test for
- isDraggedLeft = pipApp.isCloserToRightEdge(wmHelper)
- }
- teardown {
- // release the primary pointer after dragging without release
- pipApp.releasePipAfterDragging()
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ val stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
+ setup {
+ tapl.setEnableRotation(true)
+ pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
- pipApp.exit(wmHelper)
- tapl.setEnableRotation(false)
- }
- transitions { pipApp.dragPipWindowAwayFromEdgeWithoutRelease(wmHelper, 50) }
+ // determine the direction of dragging to test for
+ isDraggedLeft = pipApp.isCloserToRightEdge(wmHelper)
+ }
+ }
+
+ override val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown {
+ // release the primary pointer after dragging without release
+ pipApp.releasePipAfterDragging()
+
+ pipApp.exit(wmHelper)
+ tapl.setEnableRotation(false)
}
+ }
@Postsubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 8eb41b4ac694..60bf5ffdc7af 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -37,8 +37,9 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 270677470)
class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) {
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
+ }
/** Checks that the visible region area of [pipApp] always decreases during the animation. */
@Postsubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index eb1245b9ab86..17a178f78de3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -56,27 +56,37 @@ abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) {
}
}
- /**
- * Gets a configuration that handles basic setup and teardown of pip tests and that launches the
- * Pip app for test
- *
- * @param stringExtras Arguments to pass to the PIP launch intent
- * @param extraSpec Additional segment of flicker specification
- */
- @JvmOverloads
- protected open fun buildTransition(
- stringExtras: Map<String, String> = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true"),
- extraSpec: FlickerBuilder.() -> Unit = {}
- ): FlickerBuilder.() -> Unit {
- return {
- setup {
- setRotation(Rotation.ROTATION_0)
- removeAllTasksButHome()
- pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
- }
- teardown { pipApp.exit(wmHelper) }
+ /** Defines the transition used to run the test */
+ protected open val thisTransition: FlickerBuilder.() -> Unit = {}
- extraSpec(this)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ defaultSetup(this)
+ defaultEnterPip(this)
+ thisTransition(this)
+ defaultTeardown(this)
+ }
+
+ /** Defines the default setup steps required by the test */
+ protected open val defaultSetup: FlickerBuilder.() -> Unit = {
+ setup {
+ setRotation(Rotation.ROTATION_0)
+ removeAllTasksButHome()
+ }
+ }
+
+ /** Defines the default method of entering PiP */
+ protected open val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ pipApp.launchViaIntentAndWaitForPip(wmHelper,
+ stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true"))
+ }
+ }
+
+ /** Defines the default teardown required to clean up after the test */
+ protected open val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown {
+ pipApp.exit(wmHelper)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index 3850c1f6c89a..c618e5a24fdf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -50,41 +50,41 @@ open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransit
private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- // Launch the PiP activity fixed as landscape.
- pipApp.launchViaIntent(
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions {
+ // Launch the activity back into fullscreen and ensure that it is now in landscape
+ pipApp.launchViaIntent(wmHelper)
+ // System bar may fade out during fixed rotation.
+ wmHelper
+ .StateSyncBuilder()
+ .withFullScreenApp(pipApp)
+ .withRotation(Rotation.ROTATION_90)
+ .withNavOrTaskBarVisible()
+ .withStatusBarVisible()
+ .waitForAndVerify()
+ }
+ }
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ // Launch the PiP activity fixed as landscape.
+ pipApp.launchViaIntent(
wmHelper,
stringExtras =
- mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
- )
- // Enter PiP.
- broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP)
- // System bar may fade out during fixed rotation.
- wmHelper
+ mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+ )
+ // Enter PiP.
+ broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP)
+ // System bar may fade out during fixed rotation.
+ wmHelper
.StateSyncBuilder()
.withPipShown()
.withRotation(Rotation.ROTATION_0)
.withNavOrTaskBarVisible()
.withStatusBarVisible()
.waitForAndVerify()
- }
- teardown { pipApp.exit(wmHelper) }
- transitions {
- // Launch the activity back into fullscreen and ensure that it is now in landscape
- pipApp.launchViaIntent(wmHelper)
- // System bar may fade out during fixed rotation.
- wmHelper
- .StateSyncBuilder()
- .withFullScreenApp(pipApp)
- .withRotation(Rotation.ROTATION_90)
- .withNavOrTaskBarVisible()
- .withStatusBarVisible()
- .waitForAndVerify()
- }
}
+ }
/**
* This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index 703784dd8c67..43d6c8f26126 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -63,14 +63,13 @@ open class ShowPipAndRotateDisplay(flicker: FlickerTest) : PipTransition(flicker
private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- setup {
- testApp.launchViaIntent(wmHelper)
- setRotation(flicker.scenario.startRotation)
- }
- transitions { setRotation(flicker.scenario.endRotation) }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ testApp.launchViaIntent(wmHelper)
+ setRotation(flicker.scenario.startRotation)
}
+ transitions { setRotation(flicker.scenario.endRotation) }
+ }
/** Checks that [testApp] layer is within [screenBoundsStart] at the start of the transition */
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index ada3455fae18..1dfdbf6514ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -29,6 +29,7 @@ import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
+import android.view.ViewConfiguration;
import androidx.test.filters.SmallTest;
@@ -90,6 +91,8 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
private PipDisplayLayoutState mPipDisplayLayoutState;
+ private PipTouchState mPipTouchState;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -104,8 +107,12 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);
+
+ mPipTouchState = new PipTouchState(ViewConfiguration.get(mContext),
+ () -> {}, () -> {}, mMainExecutor);
mPipResizeGestureHandler = new PipResizeGestureHandler(mContext, pipBoundsAlgorithm,
- mPipBoundsState, motionHelper, mPipTaskOrganizer, mPipDismissTargetHandler,
+ mPipBoundsState, motionHelper, mPipTouchState, mPipTaskOrganizer,
+ mPipDismissTargetHandler,
(Rect bounds) -> new Rect(), () -> {}, mPipUiEventLogger, mPhonePipMenuController,
mMainExecutor) {
@Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
index 425bbf056b85..1379aedc2284 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
@@ -106,21 +106,21 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
sExpectedDefaultSizes = new HashMap<>();
sExpectedMinSizes = new HashMap<>();
- sExpectedMaxSizes.put(16f / 9, new Size(1000, 562));
- sExpectedDefaultSizes.put(16f / 9, new Size(600, 337));
- sExpectedMinSizes.put(16f / 9, new Size(499, 281));
+ sExpectedMaxSizes.put(16f / 9, new Size(1000, 563));
+ sExpectedDefaultSizes.put(16f / 9, new Size(600, 338));
+ sExpectedMinSizes.put(16f / 9, new Size(501, 282));
sExpectedMaxSizes.put(4f / 3, new Size(892, 669));
sExpectedDefaultSizes.put(4f / 3, new Size(535, 401));
- sExpectedMinSizes.put(4f / 3, new Size(445, 334));
+ sExpectedMinSizes.put(4f / 3, new Size(447, 335));
sExpectedMaxSizes.put(3f / 4, new Size(669, 892));
sExpectedDefaultSizes.put(3f / 4, new Size(401, 535));
- sExpectedMinSizes.put(3f / 4, new Size(334, 445));
+ sExpectedMinSizes.put(3f / 4, new Size(335, 447));
sExpectedMaxSizes.put(9f / 16, new Size(562, 999));
sExpectedDefaultSizes.put(9f / 16, new Size(337, 599));
- sExpectedMinSizes.put(9f / 16, new Size(281, 499));
+ sExpectedMinSizes.put(9f / 16, new Size(281, 500));
}
private void forEveryTestCaseCheck(Map<Float, Size> expectedSizes,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index cf962d1d94aa..bce86c477e77 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -118,8 +118,10 @@ class CredentialSelectorViewModel(
if (entry != null && entry.pendingIntent != null) {
Log.d(Constants.LOG_TAG, "Launching provider activity")
uiState = uiState.copy(providerActivityState = ProviderActivityState.PENDING)
+ val entryIntent = entry.fillInIntent
+ entryIntent?.putExtra(Constants.IS_AUTO_SELECTED_KEY, uiState.isAutoSelectFlow)
val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
- .setFillInIntent(entry.fillInIntent).build()
+ .setFillInIntent(entryIntent).build()
try {
launcher.launch(intentSenderRequest)
} catch (e: Exception) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
index c6dc5945d886..51ca5971cec4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
@@ -21,5 +21,6 @@ class Constants {
const val LOG_TAG = "CredentialSelector"
const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
"androidx.credentials.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED"
+ const val IS_AUTO_SELECTED_KEY = "IS_AUTO_SELECTED"
}
-} \ No newline at end of file
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 89bfa0eb646b..030b70a75a8b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -31,6 +31,8 @@ import com.android.settingslib.spaprivileged.template.common.UserProfilePager
/**
* The full screen template for an App List page.
*
+ * @param noMoreOptions default false. If true, then do not display more options action button,
+ * including the "Show System" / "Hide System" action.
* @param header the description header appears before all the applications.
*/
@Composable
@@ -38,6 +40,7 @@ fun <T : AppRecord> AppListPage(
title: String,
listModel: AppListModel<T>,
showInstantApps: Boolean = false,
+ noMoreOptions: Boolean = false,
matchAnyUserForAdmin: Boolean = false,
primaryUserOnly: Boolean = false,
noItemMessage: String? = null,
@@ -49,9 +52,11 @@ fun <T : AppRecord> AppListPage(
SearchScaffold(
title = title,
actions = {
- MoreOptionsAction {
- ShowSystemAction(showSystem.value) { showSystem.value = it }
- moreOptions()
+ if (!noMoreOptions) {
+ MoreOptionsAction {
+ ShowSystemAction(showSystem.value) { showSystem.value = it }
+ moreOptions()
+ }
}
},
) { bottomPadding, searchQuery ->
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
index 06003c0cb8f9..f6f48891030a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
@@ -63,9 +63,7 @@ class AppListPageTest {
fun canShowSystem() {
val inputState by setContent()
- composeTestRule.onNodeWithContentDescription(
- context.getString(R.string.abc_action_menu_overflow_description)
- ).performClick()
+ onMoreOptions().performClick()
composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick()
val state = inputState!!.state
@@ -75,20 +73,32 @@ class AppListPageTest {
@Test
fun afterShowSystem_displayHideSystem() {
setContent()
- composeTestRule.onNodeWithContentDescription(
- context.getString(R.string.abc_action_menu_overflow_description)
- ).performClick()
+ onMoreOptions().performClick()
composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick()
- composeTestRule.onNodeWithContentDescription(
- context.getString(R.string.abc_action_menu_overflow_description)
- ).performClick()
+ onMoreOptions().performClick()
composeTestRule.onNodeWithText(context.getString(R.string.menu_hide_system))
.assertIsDisplayed()
}
+ @Test
+ fun noMoreOptions_notDisplayMoreOptions() {
+ setContent(noMoreOptions = true)
+
+ onMoreOptions().assertDoesNotExist()
+ }
+
+ @Test
+ fun noMoreOptions_showSystemIsFalse() {
+ val inputState by setContent(noMoreOptions = true)
+
+ val state = inputState!!.state
+ assertThat(state.showSystem.value).isFalse()
+ }
+
private fun setContent(
+ noMoreOptions: Boolean = false,
header: @Composable () -> Unit = {},
): State<AppListInput<TestAppRecord>?> {
val appListState = mutableStateOf<AppListInput<TestAppRecord>?>(null)
@@ -96,6 +106,7 @@ class AppListPageTest {
AppListPage(
title = TITLE,
listModel = TestAppListModel(),
+ noMoreOptions = noMoreOptions,
header = header,
appList = { appListState.value = this },
)
@@ -103,6 +114,11 @@ class AppListPageTest {
return appListState
}
+ private fun onMoreOptions() =
+ composeTestRule.onNodeWithContentDescription(
+ context.getString(R.string.abc_action_menu_overflow_description)
+ )
+
private companion object {
const val TITLE = "Title"
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 8d4aa9a7b25e..c967b568042c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -688,8 +688,12 @@ public class Utils {
continue;
}
for (int complianceWarningType : complianceWarnings) {
- if (complianceWarningType != 0) {
- return true;
+ switch (complianceWarningType) {
+ case UsbPortStatus.COMPLIANCE_WARNING_OTHER:
+ case UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY:
+ return true;
+ default:
+ break;
}
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 0637e5d27f57..4a913c87bddf 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -455,12 +455,24 @@ public class UtilsTest {
}
@Test
- public void containsIncompatibleChargers_returnTrue() {
- setupIncompatibleCharging();
+ public void containsIncompatibleChargers_complianeWarningOther_returnTrue() {
+ setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
+ assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isTrue();
+ }
+
+ @Test
+ public void containsIncompatibleChargers_complianeWarningDebug_returnTrue() {
+ setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY);
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isTrue();
}
@Test
+ public void containsIncompatibleChargers_unexpectedWarningType_returnFalse() {
+ setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_BC_1_2);
+ assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
+ }
+
+ @Test
public void containsIncompatibleChargers_emptyComplianceWarnings_returnFalse() {
setupIncompatibleCharging();
when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]);
@@ -494,12 +506,17 @@ public class UtilsTest {
}
private void setupIncompatibleCharging() {
+ setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
+ }
+
+ private void setupIncompatibleCharging(int complianceWarningType) {
final List<UsbPort> usbPorts = new ArrayList<>();
usbPorts.add(mUsbPort);
when(mUsbManager.getPorts()).thenReturn(usbPorts);
when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
when(mUsbPortStatus.isConnected()).thenReturn(true);
- when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1});
+ when(mUsbPortStatus.getComplianceWarnings())
+ .thenReturn(new int[]{complianceWarningType});
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index 83e44b69812b..7e1bfb921ca9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
@@ -18,6 +18,7 @@ package com.android.systemui.animation
import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
+import android.util.Log
import android.util.LruCache
import android.util.MathUtils
import android.util.MathUtils.abs
@@ -114,6 +115,9 @@ class FontInterpolator {
tmpInterpKey.set(start, end, progress)
val cachedFont = interpCache[tmpInterpKey]
if (cachedFont != null) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "[$progress] Interp. cache hit for $tmpInterpKey")
+ }
return cachedFont
}
@@ -159,6 +163,9 @@ class FontInterpolator {
val axesCachedFont = verFontCache[tmpVarFontKey]
if (axesCachedFont != null) {
interpCache.put(InterpKey(start, end, progress), axesCachedFont)
+ if (DEBUG) {
+ Log.d(LOG_TAG, "[$progress] Axis cache hit for $tmpVarFontKey")
+ }
return axesCachedFont
}
@@ -168,6 +175,9 @@ class FontInterpolator {
val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build()
interpCache.put(InterpKey(start, end, progress), newFont)
verFontCache.put(VarFontKey(start, newAxes), newFont)
+ if (DEBUG) {
+ Log.d(LOG_TAG, "[$progress] Cache MISS for $tmpInterpKey / $tmpVarFontKey")
+ }
return newFont
}
@@ -233,6 +243,8 @@ class FontInterpolator {
(v.coerceIn(min, max) / step).toInt() * step
companion object {
+ private const val LOG_TAG = "FontInterpolator"
+ private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG)
private val EMPTY_AXES = arrayOf<FontVariationAxis>()
// Returns true if given two font instance can be interpolated.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 00108940e6ec..16ddf0c36d9d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -26,6 +26,7 @@ import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
import android.text.Layout
import android.util.LruCache
+import kotlin.math.roundToInt
private const val DEFAULT_ANIMATION_DURATION: Long = 300
private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
@@ -63,9 +64,9 @@ class TypefaceVariantCacheImpl(
return it
}
- return TypefaceVariantCache
- .createVariantTypeface(baseTypeface, fvar)
- .also { cache.put(fvar, it) }
+ return TypefaceVariantCache.createVariantTypeface(baseTypeface, fvar).also {
+ cache.put(fvar, it)
+ }
}
}
@@ -74,7 +75,6 @@ class TypefaceVariantCacheImpl(
*
* Currently this class can provide text style animation for text weight and text size. For example
* the simple view that draws text with animating text size is like as follows:
- *
* <pre> <code>
* ```
* class SimpleTextAnimation : View {
@@ -97,6 +97,7 @@ class TypefaceVariantCacheImpl(
*/
class TextAnimator(
layout: Layout,
+ numberOfAnimationSteps: Int? = null, // Only do this number of discrete animation steps.
private val invalidateCallback: () -> Unit,
) {
var typefaceCache: TypefaceVariantCache = TypefaceVariantCacheImpl(layout.paint.typeface)
@@ -112,7 +113,8 @@ class TextAnimator(
ValueAnimator.ofFloat(1f).apply {
duration = DEFAULT_ANIMATION_DURATION
addUpdateListener {
- textInterpolator.progress = it.animatedValue as Float
+ textInterpolator.progress =
+ calculateProgress(it.animatedValue as Float, numberOfAnimationSteps)
invalidateCallback()
}
addListener(
@@ -123,6 +125,17 @@ class TextAnimator(
)
}
+ private fun calculateProgress(animProgress: Float, numberOfAnimationSteps: Int?): Float {
+ if (numberOfAnimationSteps != null) {
+ // This clamps the progress to the nearest value of "numberOfAnimationSteps"
+ // discrete values between 0 and 1f.
+ return (animProgress * numberOfAnimationSteps).roundToInt() /
+ numberOfAnimationSteps.toFloat()
+ }
+
+ return animProgress
+ }
+
sealed class PositionedGlyph {
/** Mutable X coordinate of the glyph position relative from drawing offset. */
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
index 89871fa7d875..2cd587ffbc45 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
@@ -56,7 +56,19 @@ data class TurbulenceNoiseAnimationConfig(
val easeOutDuration: Float = DEFAULT_EASING_DURATION_IN_MILLIS,
val pixelDensity: Float = 1f,
val blendMode: BlendMode = DEFAULT_BLEND_MODE,
- val onAnimationEnd: Runnable? = null
+ val onAnimationEnd: Runnable? = null,
+ /**
+ * Variants in noise. Higher number means more contrast; lower number means less contrast but
+ * make the noise dimmed. You may want to increase the [lumaMatteBlendFactor] to compensate.
+ * Expected range [0, 1].
+ */
+ val lumaMatteBlendFactor: Float = DEFAULT_LUMA_MATTE_BLEND_FACTOR,
+ /**
+ * Offset for the overall brightness in noise. Higher number makes the noise brighter. You may
+ * want to use this if you have made the noise softer using [lumaMatteBlendFactor]. Expected
+ * range [0, 1].
+ */
+ val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS
) {
companion object {
const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec
@@ -66,6 +78,8 @@ data class TurbulenceNoiseAnimationConfig(
const val DEFAULT_NOISE_SPEED_Z = 0.3f
const val DEFAULT_OPACITY = 150 // full opacity is 255.
const val DEFAULT_COLOR = Color.WHITE
+ const val DEFAULT_LUMA_MATTE_BLEND_FACTOR = 1f
+ const val DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS = 0f
const val DEFAULT_BACKGROUND_COLOR = Color.BLACK
val DEFAULT_BLEND_MODE = BlendMode.SRC_OVER
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index d1ba7c4de35c..d3c57c91405a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -37,6 +37,8 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
uniform float in_opacity;
uniform float in_pixelDensity;
uniform float in_inverseLuma;
+ uniform half in_lumaMatteBlendFactor;
+ uniform half in_lumaMatteOverallBrightness;
layout(color) uniform vec4 in_color;
layout(color) uniform vec4 in_backgroundColor;
"""
@@ -48,18 +50,21 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
uv.x *= in_aspectRatio;
vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
- float luma = abs(in_inverseLuma - simplex3d(noiseP)) * in_opacity;
+ // Bring it to [0, 1] range.
+ float luma = (simplex3d(noiseP) * in_inverseLuma) * 0.5 + 0.5;
+ luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
+ * in_opacity;
vec3 mask = maskLuminosity(in_color.rgb, luma);
vec3 color = in_backgroundColor.rgb + mask * 0.6;
- // Add dither with triangle distribution to avoid color banding. Ok to dither in the
+ // Add dither with triangle distribution to avoid color banding. Dither in the
// shader here as we are in gamma space.
float dither = triangleNoise(p * in_pixelDensity) / 255.;
// The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to
// multiply rgb with a to get the correct result.
- color = (color + dither.rrr) * in_color.a;
- return vec4(color, in_color.a);
+ color = (color + dither.rrr) * in_opacity;
+ return vec4(color, in_opacity);
}
"""
@@ -70,12 +75,15 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
uv.x *= in_aspectRatio;
vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
- float luma = abs(in_inverseLuma - simplex3d_fractal(noiseP)) * in_opacity;
+ // Bring it to [0, 1] range.
+ float luma = (simplex3d_fractal(noiseP) * in_inverseLuma) * 0.5 + 0.5;
+ luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
+ * in_opacity;
vec3 mask = maskLuminosity(in_color.rgb, luma);
vec3 color = in_backgroundColor.rgb + mask * 0.6;
// Skip dithering.
- return vec4(color * in_color.a, in_color.a);
+ return vec4(color * in_opacity, in_opacity);
}
"""
@@ -125,6 +133,28 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
}
/**
+ * Sets blend and brightness factors of the luma matte.
+ *
+ * @param lumaMatteBlendFactor increases or decreases the amount of variance in noise. Setting
+ * this a lower number removes variations. I.e. the turbulence noise will look more blended.
+ * Expected input range is [0, 1]. more dimmed.
+ * @param lumaMatteOverallBrightness adds the overall brightness of the turbulence noise.
+ * Expected input range is [0, 1].
+ *
+ * Example usage: You may want to apply a small number to [lumaMatteBlendFactor], such as 0.2,
+ * which makes the noise look softer. However it makes the overall noise look dim, so you want
+ * offset something like 0.3 for [lumaMatteOverallBrightness] to bring back its overall
+ * brightness.
+ */
+ fun setLumaMatteFactors(
+ lumaMatteBlendFactor: Float = 1f,
+ lumaMatteOverallBrightness: Float = 0f
+ ) {
+ setFloatUniform("in_lumaMatteBlendFactor", lumaMatteBlendFactor)
+ setFloatUniform("in_lumaMatteOverallBrightness", lumaMatteOverallBrightness)
+ }
+
+ /**
* Sets whether to inverse the luminosity of the noise.
*
* By default noise will be used as a luma matte as is. This means that you will see color in
@@ -132,7 +162,7 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
* true.
*/
fun setInverseNoiseLuminosity(inverse: Boolean) {
- setFloatUniform("in_inverseLuma", if (inverse) 1f else 0f)
+ setFloatUniform("in_inverseLuma", if (inverse) -1f else 1f)
}
/** Current noise movements in x, y, and z axes. */
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
index c3e84787d4fb..43d6504fce84 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -215,10 +215,12 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
noiseConfig = config
with(turbulenceNoiseShader) {
setGridCount(config.gridCount)
- setColor(ColorUtils.setAlphaComponent(config.color, config.opacity))
+ setColor(config.color)
setBackgroundColor(config.backgroundColor)
setSize(config.width, config.height)
setPixelDensity(config.pixelDensity)
+ setInverseNoiseLuminosity(inverse = false)
+ setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness)
}
paint.blendMode = config.blendMode
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 465b73e6de19..648ef03895cd 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -74,7 +74,8 @@ class AnimatableClockView @JvmOverloads constructor(
private var onTextAnimatorInitialized: Runnable? = null
@VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator =
- { layout, invalidateCb -> TextAnimator(layout, invalidateCb) }
+ { layout, invalidateCb ->
+ TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) }
@VisibleForTesting var isAnimationEnabled: Boolean = true
@VisibleForTesting var timeOverrideInMillis: Long? = null
@@ -567,6 +568,7 @@ class AnimatableClockView @JvmOverloads constructor(
private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
private const val COLOR_ANIM_DURATION: Long = 400
+ private const val NUM_CLOCK_FONT_ANIMATION_STEPS = 30
// Constants for the animation
private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
index a8f0cc3a1d92..4a9d41fae1d5 100644
--- a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
@@ -14,20 +14,6 @@ Copyright (C) 2015 The Android Open Source Project
limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetLeft="3dp"
- android:insetRight="3dp">
- <vector android:width="18dp"
- android:height="18dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4C10,21.1 10.9,22 12,22z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M16,16L2.81,2.81L1.39,4.22l4.85,4.85C6.09,9.68 6,10.33 6,11v6H4v2h12.17l3.61,3.61l1.41,-1.41L16,16zM8,17c0,0 0.01,-6.11 0.01,-6.16L14.17,17H8z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12,6.5c2.49,0 4,2.02 4,4.5v2.17l2,2V11c0,-3.07 -1.63,-5.64 -4.5,-6.32V4c0,-0.83 -0.67,-1.5 -1.5,-1.5S10.5,3.17 10.5,4v0.68C9.72,4.86 9.05,5.2 8.46,5.63L9.93,7.1C10.51,6.73 11.2,6.5 12,6.5z"/>
- </vector>
-</inset>
+ android:insetLeft="3dp"
+ android:insetRight="3dp"
+ android:drawable="@drawable/ic_speaker_mute" />
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index 81691898dfe5..efc661a6e974 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -55,13 +55,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center">
- <com.airbnb.lottie.LottieAnimationView
- android:id="@+id/biometric_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:contentDescription="@null"
- android:scaleType="fitXY" />
+ <include layout="@layout/auth_biometric_icon"/>
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/biometric_icon_overlay"
diff --git a/packages/SystemUI/res/layout/auth_biometric_icon.xml b/packages/SystemUI/res/layout/auth_biometric_icon.xml
new file mode 100644
index 000000000000..b2df63dab700
--- /dev/null
+++ b/packages/SystemUI/res/layout/auth_biometric_icon.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.
+ -->
+
+
+<com.airbnb.lottie.LottieAnimationView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/biometric_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:contentDescription="@null"
+ android:scaleType="fitXY"/> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8d3ba364da06..4f768cc39b40 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -572,6 +572,7 @@
<dimen name="qs_brightness_margin_bottom">16dp</dimen>
<dimen name="qqs_layout_margin_top">16dp</dimen>
<dimen name="qqs_layout_padding_bottom">24dp</dimen>
+ <item name="qqs_expand_clock_scale" format="float" type="dimen">2.57</item>
<!-- Most of the time it should be the same as notification_side_paddings as it's vertically
aligned with notifications. The exception is split shade when this value becomes
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b48296fe54be..58be3e9d4e2f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2724,8 +2724,8 @@
<string name="media_output_broadcast_last_update_error">Can\u2019t save.</string>
<!-- The hint message when Broadcast code is less than 4 characters [CHAR LIMIT=60] -->
<string name="media_output_broadcast_code_hint_no_less_than_min">Use at least 4 characters</string>
- <!-- The hint message when Broadcast code is more than 16 characters [CHAR LIMIT=60] -->
- <string name="media_output_broadcast_code_hint_no_more_than_max">Use fewer than 16 characters</string>
+ <!-- The hint message when Broadcast edit is more than 16/254 characters [CHAR LIMIT=60] -->
+ <string name="media_output_broadcast_edit_hint_no_more_than_max">Use fewer than <xliff:g id="length" example="16">%1$d</xliff:g> characters</string>
<!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]-->
<string name="build_number_clip_data_label">Build number</string>
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index 52a98984e6e2..8039c68485ca 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -43,8 +43,8 @@
app:layout_constraintBottom_toBottomOf="@id/carrier_group"
/>
<Transform
- android:scaleX="2.57"
- android:scaleY="2.57"
+ android:scaleX="@dimen/qqs_expand_clock_scale"
+ android:scaleY="@dimen/qqs_expand_clock_scale"
/>
</Constraint>
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index f96d1e3d7fef..070a45170d04 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -62,6 +62,15 @@ open class ViewScreenshotTestRule(
private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
override fun apply(base: Statement, description: Description): Statement {
+ if (isRobolectric) {
+ // In robolectric mode, we enable NATIVE graphics and unpack font and icu files.
+ // We need to use reflection, as this library is only needed and therefore
+ // only available in deviceless mode.
+ val nativeLoaderClassName = "org.robolectric.nativeruntime.DefaultNativeRuntimeLoader"
+ val defaultNativeRuntimeLoader = Class.forName(nativeLoaderClassName)
+ System.setProperty("robolectric.graphicsMode", "NATIVE")
+ defaultNativeRuntimeLoader.getMethod("injectAndLoad").invoke(null)
+ }
val ruleToApply = if (isRobolectric) roboRule else delegateRule
return ruleToApply.apply(base, description)
}
@@ -69,6 +78,7 @@ open class ViewScreenshotTestRule(
protected fun takeScreenshot(
mode: Mode = Mode.WrapContent,
viewProvider: (ComponentActivity) -> View,
+ beforeScreenshot: (ComponentActivity) -> Unit = {}
): Bitmap {
activityRule.scenario.onActivity { activity ->
// Make sure that the activity draws full screen and fits the whole display instead of
@@ -94,6 +104,7 @@ open class ViewScreenshotTestRule(
val content = activity.requireViewById<ViewGroup>(android.R.id.content)
assertEquals(1, content.childCount)
contentView = content.getChildAt(0)
+ beforeScreenshot(activity)
}
return if (isRobolectric) {
@@ -111,9 +122,10 @@ open class ViewScreenshotTestRule(
fun screenshotTest(
goldenIdentifier: String,
mode: Mode = Mode.WrapContent,
- viewProvider: (ComponentActivity) -> View,
+ beforeScreenshot: (ComponentActivity) -> Unit = {},
+ viewProvider: (ComponentActivity) -> View
) {
- val bitmap = takeScreenshot(mode, viewProvider)
+ val bitmap = takeScreenshot(mode, viewProvider, beforeScreenshot)
screenshotRule.assertBitmapAgainstGolden(
bitmap,
goldenIdentifier,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 1721891550a1..83c317fe3061 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -3565,7 +3565,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
private void handleTimeUpdate() {
Assert.isMainThread();
- mLogger.d("handleTimeUpdate");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -3630,9 +3629,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void handleBatteryUpdate(BatteryStatus status) {
Assert.isMainThread();
final boolean batteryUpdateInteresting = isBatteryUpdateInteresting(mBatteryStatus, status);
- mLogger.logHandleBatteryUpdate(batteryUpdateInteresting);
mBatteryStatus = status;
if (batteryUpdateInteresting) {
+ mLogger.logHandleBatteryUpdate(mBatteryStatus);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 4923ab0fab18..b5963312cb2d 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -30,7 +30,7 @@ import com.android.keyguard.FaceAuthUiEvent
import com.android.keyguard.KeyguardListenModel
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.TrustGrantFlags
-import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
+import com.android.settingslib.fuelgauge.BatteryStatus
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.LogLevel.DEBUG
@@ -38,6 +38,7 @@ import com.android.systemui.log.LogLevel.ERROR
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.LogLevel.VERBOSE
import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
@@ -683,8 +684,27 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
)
}
- fun logHandleBatteryUpdate(isInteresting: Boolean) {
- logBuffer.log(TAG, DEBUG, { bool1 = isInteresting }, { "handleBatteryUpdate: $bool1" })
+ fun logHandleBatteryUpdate(batteryStatus: BatteryStatus?) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = batteryStatus != null
+ int1 = batteryStatus?.status ?: -1
+ int2 = batteryStatus?.chargingStatus ?: -1
+ long1 = (batteryStatus?.level ?: -1).toLong()
+ long2 = (batteryStatus?.maxChargingWattage ?: -1).toLong()
+ str1 = "${batteryStatus?.plugged ?: -1}"
+ },
+ {
+ "handleBatteryUpdate: isNotNull: $bool1 " +
+ "BatteryStatus{status= $int1, " +
+ "level=$long1, " +
+ "plugged=$str1, " +
+ "chargingStatus=$int2, " +
+ "maxChargingWattage= $long2}"
+ }
+ )
}
fun scheduleWatchdog(@CompileTimeConstant watchdogType: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index c30a2146436c..8af92ce4bcb1 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -548,6 +548,10 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
if (!cancelled) {
updateSwipeProgressFromOffset(animView, canBeDismissed);
resetSwipeOfView(animView);
+ // Clear the snapped view after success, assuming it's not being swiped now
+ if (animView == mTouchedView && !mIsSwiping) {
+ mTouchedView = null;
+ }
}
onChildSnappedBack(animView, targetLeft);
});
@@ -813,13 +817,39 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
}
}
- public void resetSwipeState() {
- View swipedView = getSwipedView();
+ private void resetSwipeState() {
+ resetSwipeStates(/* resetAll= */ false);
+ }
+
+ public void resetTouchState() {
+ resetSwipeStates(/* resetAll= */ true);
+ }
+
+ /** This method resets the swipe state, and if `resetAll` is true, also resets the snap state */
+ private void resetSwipeStates(boolean resetAll) {
+ final View touchedView = mTouchedView;
+ final boolean wasSnapping = mSnappingChild;
+ final boolean wasSwiping = mIsSwiping;
mTouchedView = null;
mIsSwiping = false;
- if (swipedView != null) {
- snapChildIfNeeded(swipedView, false, 0);
- onChildSnappedBack(swipedView, 0);
+ // If we were swiping, then we resetting swipe requires resetting everything.
+ resetAll |= wasSwiping;
+ if (resetAll) {
+ mSnappingChild = false;
+ }
+ if (touchedView == null) return; // No view to reset visually
+ // When snap needs to be reset, first thing is to cancel any translation animation
+ final boolean snapNeedsReset = resetAll && wasSnapping;
+ if (snapNeedsReset) {
+ cancelTranslateAnimation(touchedView);
+ }
+ // actually reset the view to default state
+ if (resetAll) {
+ snapChildIfNeeded(touchedView, false, 0);
+ }
+ // report if a swipe or snap was reset.
+ if (wasSwiping || snapNeedsReset) {
+ onChildSnappedBack(touchedView, 0);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 69ce78ce30a8..cd8f04d18500 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -961,6 +961,10 @@ public abstract class AuthBiometricView extends LinearLayout implements AuthBiom
return Utils.isDeviceCredentialAllowed(mPromptInfo);
}
+ public LottieAnimationView getIconView() {
+ return mIconView;
+ }
+
@AuthDialog.DialogSize int getSize() {
return mSize;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 43a3b9958ee5..7f706859abb3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -614,7 +614,11 @@ public class AuthContainerView extends LinearLayout
return ((AuthBiometricFingerprintView) view).isUdfps();
}
if (view instanceof BiometricPromptLayout) {
- return ((BiometricPromptLayout) view).isUdfps();
+ // this will force the prompt to align itself on the edge of the screen
+ // instead of centering (temporary workaround to prevent small implicit view
+ // from breaking due to the way gravity / margins are set in the legacy
+ // AuthPanelController
+ return true;
}
return false;
@@ -638,12 +642,12 @@ public class AuthContainerView extends LinearLayout
case Surface.ROTATION_90:
mPanelController.setPosition(AuthPanelController.POSITION_RIGHT);
- setScrollViewGravity(Gravity.BOTTOM | Gravity.RIGHT);
+ setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
break;
case Surface.ROTATION_270:
mPanelController.setPosition(AuthPanelController.POSITION_LEFT);
- setScrollViewGravity(Gravity.BOTTOM | Gravity.LEFT);
+ setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
break;
case Surface.ROTATION_180:
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
index acdde3404ab5..167067e7d7e9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
@@ -114,7 +114,13 @@ public class AuthPanelController extends ViewOutlineProvider {
}
private int getTopBound(@Position int position) {
- return Math.max(mContainerHeight - mContentHeight - mMargin, mMargin);
+ switch (position) {
+ case POSITION_LEFT:
+ case POSITION_RIGHT:
+ return Math.max((mContainerHeight - mContentHeight) / 2, mMargin);
+ default:
+ return Math.max(mContainerHeight - mContentHeight - mMargin, mMargin);
+ }
}
public void setContainerDimensions(int containerWidth, int containerHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index baaa96efb5f0..d48b9c339d15 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -35,6 +35,7 @@ import android.os.Handler
import android.util.Log
import android.util.RotationUtils
import android.view.Display
+import android.view.DisplayInfo
import android.view.Gravity
import android.view.LayoutInflater
import android.view.Surface
@@ -58,6 +59,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.util.boundsOnScreen
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.traceSection
import java.io.PrintWriter
@@ -129,6 +131,8 @@ constructor(
}
@VisibleForTesting var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
+ private val displayInfo = DisplayInfo()
+
private val overlayViewParams =
WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
@@ -214,6 +218,23 @@ constructor(
for (requestSource in requests) {
pw.println(" $requestSource.name")
}
+
+ pw.println("overlayView:")
+ pw.println(" width=${overlayView?.width}")
+ pw.println(" height=${overlayView?.height}")
+ pw.println(" boundsOnScreen=${overlayView?.boundsOnScreen}")
+
+ pw.println("displayStateInteractor:")
+ pw.println(" isInRearDisplayMode=${displayStateInteractor?.isInRearDisplayMode?.value}")
+
+ pw.println("sensorProps:")
+ pw.println(" displayId=${displayInfo.uniqueId}")
+ pw.println(" sensorType=${sensorProps?.sensorType}")
+ pw.println(" location=${sensorProps?.getLocation(displayInfo.uniqueId)}")
+
+ pw.println("overlayOffsets=$overlayOffsets")
+ pw.println("isReverseDefaultRotation=$isReverseDefaultRotation")
+ pw.println("currentRotation=${displayInfo.rotation}")
}
private fun onOrientationChanged(@BiometricOverlayConstants.ShowReason reason: Int) {
@@ -226,6 +247,8 @@ constructor(
val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
overlayView = view
val display = context.display!!
+ // b/284098873 `context.display.rotation` may not up-to-date, we use displayInfo.rotation
+ display.getDisplayInfo(displayInfo)
val offsets =
sensorProps.getLocation(display.uniqueId).let { location ->
if (location == null) {
@@ -239,12 +262,12 @@ constructor(
view.rotation =
display.asSideFpsAnimationRotation(
offsets.isYAligned(),
- getRotationFromDefault(display.rotation)
+ getRotationFromDefault(displayInfo.rotation)
)
lottie.setAnimation(
display.asSideFpsAnimation(
offsets.isYAligned(),
- getRotationFromDefault(display.rotation)
+ getRotationFromDefault(displayInfo.rotation)
)
)
lottie.addLottieOnCompositionLoadedListener {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index c935aa290e21..26b6f2a7a3cc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -78,6 +78,7 @@ constructor(
sendFoldStateUpdate(isFolded)
}
}
+
sendFoldStateUpdate(false)
screenSizeFoldProvider.registerCallback(callback, mainExecutor)
awaitClose { screenSizeFoldProvider.unregisterCallback(callback) }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintIconViewBinder.kt
new file mode 100644
index 000000000000..bd0907e588ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintIconViewBinder.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.biometrics.ui.binder
+
+import android.view.DisplayInfo
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.android.systemui.biometrics.AuthBiometricFingerprintView
+import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.launch
+
+/** Sub-binder for [AuthBiometricFingerprintView.mIconView]. */
+object AuthBiometricFingerprintIconViewBinder {
+
+ /**
+ * Binds a [AuthBiometricFingerprintView.mIconView] to a [AuthBiometricFingerprintViewModel].
+ */
+ @JvmStatic
+ fun bind(view: LottieAnimationView, viewModel: AuthBiometricFingerprintViewModel) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ val displayInfo = DisplayInfo()
+ view.context.display?.getDisplayInfo(displayInfo)
+ viewModel.setRotation(displayInfo.rotation)
+ viewModel.onConfigurationChanged(view.context.resources.configuration)
+ launch { viewModel.iconAsset.collect { iconAsset -> view.setAnimation(iconAsset) } }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt
index ae0cf3771ed3..9c1bcec2f396 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt
@@ -17,31 +17,18 @@
package com.android.systemui.biometrics.ui.binder
-import android.view.Surface
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.biometrics.AuthBiometricFingerprintView
import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.launch
object AuthBiometricFingerprintViewBinder {
- /** Binds a [AuthBiometricFingerprintView] to a [AuthBiometricFingerprintViewModel]. */
+ /**
+ * Binds a [AuthBiometricFingerprintView.mIconView] to a [AuthBiometricFingerprintViewModel].
+ */
@JvmStatic
fun bind(view: AuthBiometricFingerprintView, viewModel: AuthBiometricFingerprintViewModel) {
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.onConfigurationChanged(view.context.resources.configuration)
- viewModel.setRotation(view.context.display?.orientation ?: Surface.ROTATION_0)
- launch {
- viewModel.iconAsset.collect { iconAsset ->
- if (view.isSfps) {
- view.updateIconViewAnimation(iconAsset)
- }
- }
- }
- }
+ if (view.isSfps) {
+ AuthBiometricFingerprintIconViewBinder.bind(view.getIconView(), viewModel)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index e4c4e9aedb56..1dffa80a084f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -19,8 +19,11 @@ package com.android.systemui.biometrics.ui.binder
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
+import android.view.Surface
import android.view.View
import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.widget.TextView
import androidx.core.animation.addListener
@@ -52,7 +55,9 @@ object BiometricViewSizeBinder {
panelViewController: AuthPanelController,
jankListener: BiometricJankListener,
) {
- val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
+ val windowManager = requireNotNull(view.context.getSystemService(WindowManager::class.java))
+ val accessibilityManager =
+ requireNotNull(view.context.getSystemService(AccessibilityManager::class.java))
fun notifyAccessibilityChanged() {
Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
}
@@ -102,15 +107,26 @@ object BiometricViewSizeBinder {
when {
size.isSmall -> {
iconHolderView.alpha = 1f
+ val bottomInset =
+ windowManager.maximumWindowMetrics.windowInsets
+ .getInsets(WindowInsets.Type.navigationBars())
+ .bottom
iconHolderView.y =
- view.height - iconHolderView.height - iconPadding
+ if (view.isLandscape()) {
+ (view.height - iconHolderView.height - bottomInset) / 2f
+ } else {
+ view.height -
+ iconHolderView.height -
+ iconPadding -
+ bottomInset
+ }
val newHeight =
- iconHolderView.height + 2 * iconPadding.toInt() -
+ iconHolderView.height + (2 * iconPadding.toInt()) -
iconHolderView.paddingTop -
iconHolderView.paddingBottom
panelViewController.updateForContentDimensions(
width,
- newHeight,
+ newHeight + bottomInset,
0, /* animateDurationMs */
)
}
@@ -181,6 +197,11 @@ object BiometricViewSizeBinder {
}
}
+private fun View.isLandscape(): Boolean {
+ val r = context.display.rotation
+ return r == Surface.ROTATION_90 || r == Surface.ROTATION_270
+}
+
private fun TextView.showTextOrHide(forceHide: Boolean = false) {
visibility = if (forceHide || text.isBlank()) View.GONE else View.VISIBLE
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index 78e132ff6397..4b297a3d321d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -33,10 +33,10 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
-import com.android.systemui.smartspace.dagger.SmartspaceModule
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_WEATHER_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
import com.android.systemui.util.concurrency.Execution
import java.util.Optional
@@ -58,7 +58,7 @@ class DreamSmartspaceController @Inject constructor(
@Named(DREAM_SMARTSPACE_TARGET_FILTER)
private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
@Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>,
- @Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN)
+ @Named(DREAM_WEATHER_SMARTSPACE_DATA_PLUGIN)
optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
) {
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e5fa2090bf0f..a7e5c22e0380 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -728,6 +728,13 @@ object Flags {
@JvmField
val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter")
+ // 2900 - Zero Jank fixes. Naming convention is: zj_<bug number>_<cuj name>
+
+ // TODO:(b/285623104): Tracking bug
+ @JvmField
+ val ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD =
+ releasedFlag(2900, "zj_285570694_lockscreen_transition_from_aod")
+
// TODO(b/283084712): Tracking Bug
@JvmField
val IMPROVED_HUN_ANIMATIONS = unreleasedFlag(283084712, "improved_hun_animations")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 5a8c2253b185..94227bccfced 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
@@ -182,7 +183,8 @@ public class KeyguardService extends Service {
// Wrap Keyguard going away animation.
// Note: Also used for wrapping occlude by Dream animation. It works (with some redundancy).
- public static IRemoteTransition wrap(IRemoteAnimationRunner runner) {
+ public static IRemoteTransition wrap(final KeyguardViewMediator keyguardViewMediator,
+ final IRemoteAnimationRunner runner, final boolean lockscreenLiveWallpaperEnabled) {
return new IRemoteTransition.Stub() {
private final ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = new ArrayMap<>();
@@ -213,7 +215,9 @@ public class KeyguardService extends Service {
}
}
initAlphaForAnimationTargets(t, apps);
- initAlphaForAnimationTargets(t, wallpapers);
+ if (lockscreenLiveWallpaperEnabled) {
+ initAlphaForAnimationTargets(t, wallpapers);
+ }
t.apply();
mFinishCallback = finishCallback;
runner.onAnimationStart(
@@ -236,6 +240,12 @@ public class KeyguardService extends Service {
SurfaceControl.Transaction candidateT, IBinder currentTransition,
IRemoteTransitionFinishedCallback candidateFinishCallback)
throws RemoteException {
+ if ((candidateInfo.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) {
+ keyguardViewMediator.setPendingLock(true);
+ keyguardViewMediator.cancelKeyguardExitAnimation();
+ return;
+ }
+
try {
synchronized (mLeashMap) {
runner.onAnimationCancelled();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index c706363c9454..68e72c58972b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
+import android.app.WallpaperManager
import android.content.Context
import android.graphics.Matrix
import android.graphics.Rect
@@ -50,6 +51,7 @@ import com.android.systemui.shared.system.smartspace.SmartspaceState
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
import javax.inject.Inject
@@ -148,7 +150,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
private val statusBarStateController: SysuiStatusBarStateController,
private val notificationShadeWindowController: NotificationShadeWindowController,
- private val powerManager: PowerManager
+ private val powerManager: PowerManager,
+ private val wallpaperManager: WallpaperManager
) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
interface KeyguardUnlockAnimationListener {
@@ -171,7 +174,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
@JvmDefault
fun onUnlockAnimationStarted(
playingCannedAnimation: Boolean,
- fromWakeAndUnlock: Boolean,
+ isWakeAndUnlockNotFromDream: Boolean,
unlockAnimationStartDelay: Long,
unlockAnimationDuration: Long
) {}
@@ -204,6 +207,12 @@ class KeyguardUnlockAnimationController @Inject constructor(
var playingCannedUnlockAnimation = false
/**
+ * Whether we reached the swipe gesture threshold to dismiss keyguard, or restore it, once
+ * and should ignore any future changes to the dismiss amount before the animation finishes.
+ */
+ var dismissAmountThresholdsReached = false
+
+ /**
* Remote callback provided by Launcher that allows us to control the Launcher's unlock
* animation and smartspace.
*
@@ -582,10 +591,13 @@ class KeyguardUnlockAnimationController @Inject constructor(
playCannedUnlockAnimation()
}
+ // Notify if waking from AOD only
+ val isWakeAndUnlockNotFromDream = biometricUnlockControllerLazy.get().isWakeAndUnlock &&
+ biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
listeners.forEach {
it.onUnlockAnimationStarted(
playingCannedUnlockAnimation /* playingCannedAnimation */,
- biometricUnlockControllerLazy.get().isWakeAndUnlock /* isWakeAndUnlock */,
+ isWakeAndUnlockNotFromDream /* isWakeAndUnlockNotFromDream */,
CANNED_UNLOCK_START_DELAY /* unlockStartDelay */,
LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */) }
@@ -647,6 +659,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
@VisibleForTesting
fun unlockToLauncherWithInWindowAnimations() {
+ surfaceBehindAlpha = 1f
setSurfaceBehindAppearAmount(1f, wallpapers = false)
try {
@@ -686,8 +699,10 @@ class KeyguardUnlockAnimationController @Inject constructor(
return@postDelayed
}
- if (wallpaperTargets != null) {
- fadeInWallpaper()
+ if ((wallpaperTargets?.isNotEmpty() == true) &&
+ wallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+ fadeInWallpaper()
+ hideKeyguardViewAfterRemoteAnimation()
} else {
keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
false /* cancelled */)
@@ -758,6 +773,10 @@ class KeyguardUnlockAnimationController @Inject constructor(
return
}
+ if (dismissAmountThresholdsReached) {
+ return
+ }
+
if (!keyguardStateController.isShowing) {
return
}
@@ -789,6 +808,11 @@ class KeyguardUnlockAnimationController @Inject constructor(
return
}
+ // no-op if we alreaddy reached a threshold.
+ if (dismissAmountThresholdsReached) {
+ return
+ }
+
// no-op if animation is not requested yet.
if (!keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
!keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) {
@@ -803,6 +827,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
!keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)) {
setSurfaceBehindAppearAmount(1f)
+ dismissAmountThresholdsReached = true
keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
false /* cancelled */)
}
@@ -937,6 +962,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
wallpaperTargets = null
playingCannedUnlockAnimation = false
+ dismissAmountThresholdsReached = false
willUnlockWithInWindowLauncherAnimations = false
willUnlockWithSmartspaceTransition = false
@@ -961,7 +987,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
0 /* fadeOutDuration */
)
} else {
- Log.e(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
+ Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
"showing. Ignoring...")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b4871211be9b..6948c8d4e563 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -45,6 +45,7 @@ import android.app.AlarmManager;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.app.StatusBarManager;
+import android.app.WallpaperManager;
import android.app.WindowConfiguration;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
@@ -93,7 +94,6 @@ import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
-import android.window.IRemoteTransition;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -161,7 +161,6 @@ import dagger.Lazy;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Optional;
import java.util.concurrent.Executor;
/**
@@ -280,6 +279,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
private StatusBarManager mStatusBarManager;
+ private WallpaperManager mWallpaperManager;
private final IStatusBarService mStatusBarService;
private final IBinder mStatusBarDisableToken = new Binder();
private final UserTracker mUserTracker;
@@ -1350,11 +1350,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
setShowingLocked(false /* showing */, true /* forceCallbacks */);
}
+ boolean isLLwpEnabled = getWallpaperManager().isLockscreenLiveWallpaperEnabled();
mKeyguardTransitions.register(
- KeyguardService.wrap(getExitAnimationRunner()),
- KeyguardService.wrap(getOccludeAnimationRunner()),
- KeyguardService.wrap(getOccludeByDreamAnimationRunner()),
- KeyguardService.wrap(getUnoccludeAnimationRunner()));
+ KeyguardService.wrap(this, getExitAnimationRunner(), isLLwpEnabled),
+ KeyguardService.wrap(this, getOccludeAnimationRunner(), isLLwpEnabled),
+ KeyguardService.wrap(this, getOccludeByDreamAnimationRunner(), isLLwpEnabled),
+ KeyguardService.wrap(this, getUnoccludeAnimationRunner(), isLLwpEnabled));
final ContentResolver cr = mContext.getContentResolver();
@@ -1400,6 +1401,14 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mWorkLockController = new WorkLockActivityController(mContext, mUserTracker);
}
+ // TODO(b/273443374) remove, temporary util to get a feature flag
+ private WallpaperManager getWallpaperManager() {
+ if (mWallpaperManager == null) {
+ mWallpaperManager = mContext.getSystemService(WallpaperManager.class);
+ }
+ return mWallpaperManager;
+ }
+
@Override
public void start() {
synchronized (this) {
@@ -1902,19 +1911,19 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
public IRemoteAnimationRunner getExitAnimationRunner() {
- return mExitAnimationRunner;
+ return validatingRemoteAnimationRunner(mExitAnimationRunner);
}
public IRemoteAnimationRunner getOccludeAnimationRunner() {
- return mOccludeAnimationRunner;
+ return validatingRemoteAnimationRunner(mOccludeAnimationRunner);
}
public IRemoteAnimationRunner getOccludeByDreamAnimationRunner() {
- return mOccludeByDreamAnimationRunner;
+ return validatingRemoteAnimationRunner(mOccludeByDreamAnimationRunner);
}
public IRemoteAnimationRunner getUnoccludeAnimationRunner() {
- return mUnoccludeAnimationRunner;
+ return validatingRemoteAnimationRunner(mUnoccludeAnimationRunner);
}
public boolean isHiding() {
@@ -2895,6 +2904,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// re-locking. We should just end the surface-behind animation without exiting the
// keyguard. The pending lock will be handled by onFinishedGoingToSleep().
finishSurfaceBehindRemoteAnimation(true);
+ maybeHandlePendingLock();
} else {
Log.d(TAG, "#handleCancelKeyguardExitAnimation: keyguard exit animation cancelled. "
+ "No pending lock, we should end up unlocked with the app/launcher visible.");
@@ -3250,8 +3260,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
/**
* Cancel the keyguard exit animation, usually because we were swiping to unlock but WM starts
* a new remote animation before finishing the keyguard exit animation.
- *
- * This will dismiss the keyguard.
*/
public void cancelKeyguardExitAnimation() {
Trace.beginSection("KeyguardViewMediator#cancelKeyguardExitAnimation");
@@ -3424,11 +3432,15 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
}
- private void setPendingLock(boolean hasPendingLock) {
+ public void setPendingLock(boolean hasPendingLock) {
mPendingLock = hasPendingLock;
Trace.traceCounter(Trace.TRACE_TAG_APP, "pendingLock", mPendingLock ? 1 : 0);
}
+ private boolean isViewRootReady() {
+ return mKeyguardViewControllerLazy.get().getViewRootImpl() != null;
+ }
+
public void addStateMonitorCallback(IKeyguardStateCallback callback) {
synchronized (this) {
mKeyguardStateCallbacks.add(callback);
@@ -3531,4 +3543,27 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_OCCLUSION);
}
}
+
+ private IRemoteAnimationRunner validatingRemoteAnimationRunner(IRemoteAnimationRunner wrapped) {
+ return new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationCancelled() throws RemoteException {
+ wrapped.onAnimationCancelled();
+ }
+
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback)
+ throws RemoteException {
+ if (!isViewRootReady()) {
+ Log.w(TAG, "Skipping remote animation - view root not ready");
+ return;
+ }
+
+ wrapped.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback);
+ }
+ };
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 44e74e7e339b..9db3c22dff84 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -63,7 +63,7 @@ constructor(
val hasCards = response?.walletCards?.isNotEmpty() == true
trySendWithFailureLogging(
state(
- isFeatureEnabled = walletController.isWalletEnabled,
+ isFeatureEnabled = isWalletAvailable(),
hasCard = hasCards,
tileIcon = walletController.walletClient.tileIcon,
),
@@ -100,7 +100,7 @@ constructor(
return when {
!walletController.walletClient.isWalletServiceAvailable ->
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
- !walletController.isWalletEnabled || queryCards().isEmpty() -> {
+ !isWalletAvailable() || queryCards().isEmpty() -> {
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
instructions =
listOf(
@@ -146,6 +146,11 @@ constructor(
}
}
+ private fun isWalletAvailable() =
+ with(walletController.walletClient) {
+ isWalletServiceAvailable && isWalletFeatureAvailable
+ }
+
private fun state(
isFeatureEnabled: Boolean,
hasCard: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 516fbf5ca12c..14386c1c0fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -76,7 +76,6 @@ import androidx.constraintlayout.widget.ConstraintSet;
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
import com.android.internal.widget.CachingIconView;
@@ -1211,24 +1210,24 @@ public class MediaControlPanel {
private TurbulenceNoiseAnimationConfig createTurbulenceNoiseAnimation() {
return new TurbulenceNoiseAnimationConfig(
- TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_GRID_COUNT,
+ /* gridCount= */ 2.14f,
TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
- /* noiseMoveSpeedX= */ 0f,
+ /* noiseMoveSpeedX= */ 0.42f,
/* noiseMoveSpeedY= */ 0f,
TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
/* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
- // We want to add (BlendMode.PLUS) the turbulence noise on top of the album art.
- // Thus, set the background color with alpha 0.
- /* backgroundColor= */ ColorUtils.setAlphaComponent(Color.BLACK, 0),
- TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY,
+ /* backgroundColor= */ Color.BLACK,
+ /* opacity= */ 51,
/* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
/* height= */ mMediaViewHolder.getMultiRippleView().getHeight(),
TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
- /* easeInDuration= */ 2500f,
- /* easeOutDuration= */ 2500f,
+ /* easeInDuration= */ 1350f,
+ /* easeOutDuration= */ 1350f,
this.getContext().getResources().getDisplayMetrics().density,
- BlendMode.PLUS,
- /* onAnimationEnd= */ null
+ BlendMode.SCREEN,
+ /* onAnimationEnd= */ null,
+ /* lumaMatteBlendFactor= */ 0.26f,
+ /* lumaMatteOverallBrightness= */ 0.09f
);
}
private void clearButton(final ImageButton button) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index abf0932c8407..b4578e97eda2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -41,6 +41,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.graphics.drawable.IconCompat;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.media.BluetoothMediaDevice;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.qrcode.QrCodeGenerator;
@@ -58,6 +59,17 @@ import com.google.zxing.WriterException;
public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
private static final String TAG = "MediaOutputBroadcastDialog";
+ static final int METADATA_BROADCAST_NAME = 0;
+ static final int METADATA_BROADCAST_CODE = 1;
+
+ private static final int MAX_BROADCAST_INFO_UPDATE = 3;
+ @VisibleForTesting
+ static final int BROADCAST_CODE_MAX_LENGTH = 16;
+ @VisibleForTesting
+ static final int BROADCAST_CODE_MIN_LENGTH = 4;
+ @VisibleForTesting
+ static final int BROADCAST_NAME_MAX_LENGTH = 254;
+
private ViewStub mBroadcastInfoArea;
private ImageView mBroadcastQrCodeView;
private ImageView mBroadcastNotify;
@@ -67,14 +79,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
private ImageView mBroadcastCodeEye;
private Boolean mIsPasswordHide = true;
private ImageView mBroadcastCodeEdit;
- private AlertDialog mAlertDialog;
+ @VisibleForTesting
+ AlertDialog mAlertDialog;
private TextView mBroadcastErrorMessage;
private int mRetryCount = 0;
private String mCurrentBroadcastName;
private String mCurrentBroadcastCode;
private boolean mIsStopbyUpdateBroadcastCode = false;
+ private boolean mIsLeBroadcastAssistantCallbackRegistered;
- private TextWatcher mTextWatcher = new TextWatcher() {
+ private TextWatcher mBroadcastCodeTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Do nothing
@@ -102,7 +116,9 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
R.string.media_output_broadcast_code_hint_no_less_than_min);
} else if (breakBroadcastCodeRuleTextLengthMoreThanMax) {
mBroadcastErrorMessage.setText(
- R.string.media_output_broadcast_code_hint_no_more_than_max);
+ mContext.getResources().getString(
+ R.string.media_output_broadcast_edit_hint_no_more_than_max,
+ BROADCAST_CODE_MAX_LENGTH));
}
mBroadcastErrorMessage.setVisibility(breakRule ? View.VISIBLE : View.INVISIBLE);
@@ -113,7 +129,40 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
};
- private boolean mIsLeBroadcastAssistantCallbackRegistered;
+ private TextWatcher mBroadcastNameTextWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // Do nothing
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (mAlertDialog == null || mBroadcastErrorMessage == null) {
+ return;
+ }
+ boolean breakBroadcastNameRuleTextLengthMoreThanMax =
+ s.length() > BROADCAST_NAME_MAX_LENGTH;
+ boolean breakRule = breakBroadcastNameRuleTextLengthMoreThanMax || (s.length() == 0);
+
+ if (breakBroadcastNameRuleTextLengthMoreThanMax) {
+ mBroadcastErrorMessage.setText(
+ mContext.getResources().getString(
+ R.string.media_output_broadcast_edit_hint_no_more_than_max,
+ BROADCAST_NAME_MAX_LENGTH));
+ }
+ mBroadcastErrorMessage.setVisibility(
+ breakBroadcastNameRuleTextLengthMoreThanMax ? View.VISIBLE : View.INVISIBLE);
+ Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ if (positiveBtn != null) {
+ positiveBtn.setEnabled(breakRule ? false : true);
+ }
+ }
+ };
private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
new BluetoothLeBroadcastAssistant.Callback() {
@@ -186,13 +235,6 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
};
- static final int METADATA_BROADCAST_NAME = 0;
- static final int METADATA_BROADCAST_CODE = 1;
-
- private static final int MAX_BROADCAST_INFO_UPDATE = 3;
- private static final int BROADCAST_CODE_MAX_LENGTH = 16;
- private static final int BROADCAST_CODE_MIN_LENGTH = 4;
-
MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar,
BroadcastSender broadcastSender, MediaOutputController mediaOutputController) {
super(context, broadcastSender, mediaOutputController);
@@ -391,13 +433,12 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
R.layout.media_output_broadcast_update_dialog, null);
final EditText editText = layout.requireViewById(R.id.broadcast_edit_text);
editText.setText(editString);
- if (isBroadcastCode) {
- editText.addTextChangedListener(mTextWatcher);
- }
+ editText.addTextChangedListener(
+ isBroadcastCode ? mBroadcastCodeTextWatcher : mBroadcastNameTextWatcher);
mBroadcastErrorMessage = layout.requireViewById(R.id.broadcast_error_message);
mAlertDialog = new Builder(mContext)
.setTitle(isBroadcastCode ? R.string.media_output_broadcast_code
- : R.string.media_output_broadcast_name)
+ : R.string.media_output_broadcast_name)
.setView(layout)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.media_output_broadcast_dialog_save,
@@ -420,7 +461,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
return mMediaOutputController.getBroadcastMetadata();
}
- private void updateBroadcastInfo(boolean isBroadcastCode, String updatedString) {
+ @VisibleForTesting
+ void updateBroadcastInfo(boolean isBroadcastCode, String updatedString) {
Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
if (positiveBtn != null) {
positiveBtn.setEnabled(false);
@@ -523,16 +565,33 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
private void handleUpdateFailedUi() {
- final Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
- mBroadcastErrorMessage.setVisibility(View.VISIBLE);
+ if (mAlertDialog == null) {
+ Log.d(TAG, "handleUpdateFailedUi: mAlertDialog is null");
+ return;
+ }
+ int errorMessageStringId = -1;
+ boolean enablePositiveBtn = false;
if (mRetryCount < MAX_BROADCAST_INFO_UPDATE) {
- if (positiveBtn != null) {
- positiveBtn.setEnabled(true);
- }
- mBroadcastErrorMessage.setText(R.string.media_output_broadcast_update_error);
+ enablePositiveBtn = true;
+ errorMessageStringId = R.string.media_output_broadcast_update_error;
} else {
mRetryCount = 0;
- mBroadcastErrorMessage.setText(R.string.media_output_broadcast_last_update_error);
+ errorMessageStringId = R.string.media_output_broadcast_last_update_error;
}
+
+ // update UI
+ final Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ if (positiveBtn != null && enablePositiveBtn) {
+ positiveBtn.setEnabled(true);
+ }
+ if (mBroadcastErrorMessage != null) {
+ mBroadcastErrorMessage.setVisibility(View.VISIBLE);
+ mBroadcastErrorMessage.setText(errorMessageStringId);
+ }
+ }
+
+ @VisibleForTesting
+ int getRetryCount() {
+ return mRetryCount;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index b0fb349083e6..682335e0b419 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -76,7 +76,6 @@ import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.Display;
-import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.InsetsFrameProvider;
@@ -1730,9 +1729,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
tappableElementProvider.setInsetsSize(Insets.NONE);
}
- final DisplayCutout cutout = userContext.getDisplay().getCutout();
- final int safeInsetsLeft = cutout != null ? cutout.getSafeInsetLeft() : 0;
- final int safeInsetsRight = cutout != null ? cutout.getSafeInsetRight() : 0;
final int gestureHeight = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_gesture_height);
final boolean handlingGesture = mEdgeBackGestureHandler.isHandlingGestures();
@@ -1742,19 +1738,23 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mandatoryGestureProvider.setInsetsSize(Insets.of(0, 0, 0, gestureHeight));
}
final int gestureInsetsLeft = handlingGesture
- ? mEdgeBackGestureHandler.getEdgeWidthLeft() + safeInsetsLeft : 0;
+ ? mEdgeBackGestureHandler.getEdgeWidthLeft() : 0;
final int gestureInsetsRight = handlingGesture
- ? mEdgeBackGestureHandler.getEdgeWidthRight() + safeInsetsRight : 0;
+ ? mEdgeBackGestureHandler.getEdgeWidthRight() : 0;
return new InsetsFrameProvider[] {
navBarProvider,
tappableElementProvider,
mandatoryGestureProvider,
new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.systemGestures())
.setSource(InsetsFrameProvider.SOURCE_DISPLAY)
- .setInsetsSize(Insets.of(gestureInsetsLeft, 0, 0, 0)),
+ .setInsetsSize(Insets.of(gestureInsetsLeft, 0, 0, 0))
+ .setMinimalInsetsSizeInDisplayCutoutSafe(
+ Insets.of(gestureInsetsLeft, 0, 0, 0)),
new InsetsFrameProvider(mInsetsSourceOwner, 1, WindowInsets.Type.systemGestures())
.setSource(InsetsFrameProvider.SOURCE_DISPLAY)
.setInsetsSize(Insets.of(0, 0, gestureInsetsRight, 0))
+ .setMinimalInsetsSizeInDisplayCutoutSafe(
+ Insets.of(0, 0, gestureInsetsRight, 0))
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
index c804df8fa555..a256b59ac076 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -157,17 +157,17 @@ class BackPanel(
arrowPaint.color = Utils.getColorAttrDefaultColor(context,
if (isDeviceInNightTheme) {
- com.android.internal.R.attr.colorAccentPrimary
+ com.android.internal.R.attr.materialColorOnSecondaryContainer
} else {
- com.android.internal.R.attr.textColorPrimary
+ com.android.internal.R.attr.materialColorOnSecondaryFixed
}
)
arrowBackgroundPaint.color = Utils.getColorAttrDefaultColor(context,
if (isDeviceInNightTheme) {
- com.android.internal.R.attr.materialColorOnSecondary
+ com.android.internal.R.attr.materialColorSecondaryContainer
} else {
- com.android.internal.R.attr.colorAccentSecondary
+ com.android.internal.R.attr.materialColorSecondaryFixedDim
}
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 897b0e73dca0..5d028307a62d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -65,14 +65,12 @@ import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.DisplayTracker;
-import dagger.Lazy;
-
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
-
+import dagger.Lazy;
public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
public static final String PREFIX = "custom(";
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index a066242fd96b..d7ae575724dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -20,6 +20,8 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
+import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepositoryImpl
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -45,6 +47,11 @@ abstract class QSPipelineModule {
): CurrentTilesInteractor
@Binds
+ abstract fun provideInstalledTilesPackageRepository(
+ impl: InstalledTilesComponentRepositoryImpl
+ ): InstalledTilesComponentRepository
+
+ @Binds
@IntoMap
@ClassKey(PrototypeCoreStartable::class)
abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
new file mode 100644
index 000000000000..498f403e8c7a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.qs.pipeline.data.repository
+
+import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
+import android.annotation.WorkerThread
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.os.UserHandle
+import android.service.quicksettings.TileService
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.kotlin.isComponentActuallyEnabled
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+interface InstalledTilesComponentRepository {
+
+ fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>>
+}
+
+@SysUISingleton
+class InstalledTilesComponentRepositoryImpl
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ private val packageManager: PackageManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : InstalledTilesComponentRepository {
+
+ override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> =
+ conflatedCallbackFlow {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ trySend(Unit)
+ }
+ }
+ applicationContext.registerReceiverAsUser(
+ receiver,
+ UserHandle.of(userId),
+ INTENT_FILTER,
+ /* broadcastPermission = */ null,
+ /* scheduler = */ null
+ )
+
+ awaitClose { applicationContext.unregisterReceiver(receiver) }
+ }
+ .onStart { emit(Unit) }
+ .map { reloadComponents(userId) }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+
+ @WorkerThread
+ private fun reloadComponents(userId: Int): Set<ComponentName> {
+ return packageManager
+ .queryIntentServicesAsUser(INTENT, FLAGS, userId)
+ .mapNotNull { it.serviceInfo }
+ .filter { it.permission == BIND_QUICK_SETTINGS_TILE }
+ .filter { packageManager.isComponentActuallyEnabled(it) }
+ .mapTo(mutableSetOf()) { it.componentName }
+ }
+
+ companion object {
+ private val INTENT_FILTER =
+ IntentFilter().apply {
+ addAction(Intent.ACTION_PACKAGE_ADDED)
+ addAction(Intent.ACTION_PACKAGE_CHANGED)
+ addAction(Intent.ACTION_PACKAGE_REMOVED)
+ addAction(Intent.ACTION_PACKAGE_REPLACED)
+ addDataScheme("package")
+ }
+ private val INTENT = Intent(TileService.ACTION_QS_TILE)
+ private val FLAGS =
+ ResolveInfoFlags.of(
+ (PackageManager.GET_SERVICES or
+ PackageManager.MATCH_DIRECT_BOOT_AWARE or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE)
+ .toLong()
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 3b2362f2b326..a162d113a3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -42,6 +42,8 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
/** Repository that tracks the current tiles. */
@@ -104,6 +106,8 @@ constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
) : TileSpecRepository {
+ private val mutex = Mutex()
+
private val retailModeTiles by lazy {
resources
.getString(R.string.quick_settings_tiles_retail_mode)
@@ -145,37 +149,40 @@ constructor(
.flowOn(backgroundDispatcher)
}
- override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) {
- if (tile == TileSpec.Invalid) {
- return
- }
- val tilesList = loadTiles(userId).toMutableList()
- if (tile !in tilesList) {
- if (position < 0 || position >= tilesList.size) {
- tilesList.add(tile)
- } else {
- tilesList.add(position, tile)
+ override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) =
+ mutex.withLock {
+ if (tile == TileSpec.Invalid) {
+ return
+ }
+ val tilesList = loadTiles(userId).toMutableList()
+ if (tile !in tilesList) {
+ if (position < 0 || position >= tilesList.size) {
+ tilesList.add(tile)
+ } else {
+ tilesList.add(position, tile)
+ }
+ storeTiles(userId, tilesList)
}
- storeTiles(userId, tilesList)
}
- }
- override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) {
- if (tiles.all { it == TileSpec.Invalid }) {
- return
- }
- val tilesList = loadTiles(userId).toMutableList()
- if (tilesList.removeAll(tiles)) {
- storeTiles(userId, tilesList.toList())
+ override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) =
+ mutex.withLock {
+ if (tiles.all { it == TileSpec.Invalid }) {
+ return
+ }
+ val tilesList = loadTiles(userId).toMutableList()
+ if (tilesList.removeAll(tiles)) {
+ storeTiles(userId, tilesList.toList())
+ }
}
- }
- override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) {
- val filtered = tiles.filter { it != TileSpec.Invalid }
- if (filtered.isNotEmpty()) {
- storeTiles(userId, filtered)
+ override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) =
+ mutex.withLock {
+ val filtered = tiles.filter { it != TileSpec.Invalid }
+ if (filtered.isNotEmpty()) {
+ storeTiles(userId, filtered)
+ }
}
- }
private suspend fun loadTiles(@UserIdInt forUser: Int): List<TileSpec> {
return withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index c579f5c3061c..ff881f767b87 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -36,6 +36,7 @@ import com.android.systemui.qs.external.CustomTileStatePersister
import com.android.systemui.qs.external.TileLifecycleManager
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.domain.model.TileModel
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -52,6 +53,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
@@ -117,11 +120,13 @@ interface CurrentTilesInteractor : ProtoDumpable {
* * Platform tiles will be kept between users, with a call to [QSTile.userSwitch]
* * [CustomTile]s will only be destroyed if the user changes.
*/
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CurrentTilesInteractorImpl
@Inject
constructor(
private val tileSpecRepository: TileSpecRepository,
+ private val installedTilesComponentRepository: InstalledTilesComponentRepository,
private val userRepository: UserRepository,
private val customTileStatePersister: CustomTileStatePersister,
private val tileFactory: QSFactory,
@@ -141,7 +146,7 @@ constructor(
override val currentTiles: StateFlow<List<TileModel>> = _currentSpecsAndTiles.asStateFlow()
// This variable should only be accessed inside the collect of `startTileCollection`.
- private val specsToTiles = mutableMapOf<TileSpec, QSTile>()
+ private val specsToTiles = mutableMapOf<TileSpec, TileOrNotInstalled>()
private val currentUser = MutableStateFlow(userTracker.userId)
override val userId = currentUser.asStateFlow()
@@ -149,6 +154,20 @@ constructor(
private val _userContext = MutableStateFlow(userTracker.userContext)
override val userContext = _userContext.asStateFlow()
+ private val userAndTiles =
+ currentUser
+ .flatMapLatest { userId ->
+ tileSpecRepository.tilesSpecs(userId).map { UserAndTiles(userId, it) }
+ }
+ .distinctUntilChanged()
+ .pairwise(UserAndTiles(-1, emptyList()))
+ .flowOn(backgroundDispatcher)
+
+ private val installedPackagesWithTiles =
+ currentUser.flatMapLatest {
+ installedTilesComponentRepository.getInstalledTilesComponents(it)
+ }
+
init {
if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
startTileCollection()
@@ -158,68 +177,98 @@ constructor(
@OptIn(ExperimentalCoroutinesApi::class)
private fun startTileCollection() {
scope.launch {
- userRepository.selectedUserInfo
- .flatMapLatest { user ->
+ launch {
+ userRepository.selectedUserInfo.collect { user ->
currentUser.value = user.id
_userContext.value = userTracker.userContext
- tileSpecRepository.tilesSpecs(user.id).map { user.id to it }
}
- .distinctUntilChanged()
- .pairwise(-1 to emptyList())
- .flowOn(backgroundDispatcher)
- .collect { (old, new) ->
- val newTileList = new.second
- val userChanged = old.first != new.first
- val newUser = new.first
-
- // Destroy all tiles that are not in the new set
- specsToTiles
- .filter { it.key !in newTileList }
- .forEach { entry ->
- logger.logTileDestroyed(
- entry.key,
- if (userChanged) {
- QSPipelineLogger.TileDestroyedReason
- .TILE_NOT_PRESENT_IN_NEW_USER
- } else {
- QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
- }
- )
- entry.value.destroy()
- }
- // MutableMap will keep the insertion order
- val newTileMap = mutableMapOf<TileSpec, QSTile>()
-
- newTileList.forEach { tileSpec ->
- if (tileSpec !in newTileMap) {
- val newTile =
- if (tileSpec in specsToTiles) {
- processExistingTile(
- tileSpec,
- specsToTiles.getValue(tileSpec),
- userChanged,
- newUser
- )
- ?: createTile(tileSpec)
+ }
+
+ launch(backgroundDispatcher) {
+ userAndTiles
+ .combine(installedPackagesWithTiles) { usersAndTiles, packages ->
+ Data(
+ usersAndTiles.previousValue,
+ usersAndTiles.newValue,
+ packages,
+ )
+ }
+ .collectLatest {
+ val newTileList = it.newData.tiles
+ val userChanged = it.oldData.userId != it.newData.userId
+ val newUser = it.newData.userId
+ val components = it.installedComponents
+
+ // Destroy all tiles that are not in the new set
+ specsToTiles
+ .filter {
+ it.key !in newTileList && it.value is TileOrNotInstalled.Tile
+ }
+ .forEach { entry ->
+ logger.logTileDestroyed(
+ entry.key,
+ if (userChanged) {
+ QSPipelineLogger.TileDestroyedReason
+ .TILE_NOT_PRESENT_IN_NEW_USER
+ } else {
+ QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
+ }
+ )
+ (entry.value as TileOrNotInstalled.Tile).tile.destroy()
+ }
+ // MutableMap will keep the insertion order
+ val newTileMap = mutableMapOf<TileSpec, TileOrNotInstalled>()
+
+ newTileList.forEach { tileSpec ->
+ if (tileSpec !in newTileMap) {
+ if (
+ tileSpec is TileSpec.CustomTileSpec &&
+ tileSpec.componentName !in components
+ ) {
+ newTileMap[tileSpec] = TileOrNotInstalled.NotInstalled
} else {
- createTile(tileSpec)
+ // Create tile here will never try to create a CustomTile that
+ // is not installed
+ val newTile =
+ if (tileSpec in specsToTiles) {
+ processExistingTile(
+ tileSpec,
+ specsToTiles.getValue(tileSpec),
+ userChanged,
+ newUser
+ )
+ ?: createTile(tileSpec)
+ } else {
+ createTile(tileSpec)
+ }
+ if (newTile != null) {
+ newTileMap[tileSpec] = TileOrNotInstalled.Tile(newTile)
+ }
}
- if (newTile != null) {
- newTileMap[tileSpec] = newTile
}
}
- }
- val resolvedSpecs = newTileMap.keys.toList()
- specsToTiles.clear()
- specsToTiles.putAll(newTileMap)
- _currentSpecsAndTiles.value = newTileMap.map { TileModel(it.key, it.value) }
- if (resolvedSpecs != newTileList) {
- // There were some tiles that couldn't be created. Change the value in the
- // repository
- launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
+ val resolvedSpecs = newTileMap.keys.toList()
+ specsToTiles.clear()
+ specsToTiles.putAll(newTileMap)
+ _currentSpecsAndTiles.value =
+ newTileMap
+ .filter { it.value is TileOrNotInstalled.Tile }
+ .map {
+ TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile)
+ }
+ logger.logTilesNotInstalled(
+ newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys,
+ newUser
+ )
+ if (resolvedSpecs != newTileList) {
+ // There were some tiles that couldn't be created. Change the value in
+ // the
+ // repository
+ launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
+ }
}
- }
+ }
}
}
@@ -301,42 +350,66 @@ constructor(
private fun processExistingTile(
tileSpec: TileSpec,
- qsTile: QSTile,
+ tileOrNotInstalled: TileOrNotInstalled,
userChanged: Boolean,
user: Int,
): QSTile? {
- return when {
- !qsTile.isAvailable -> {
- logger.logTileDestroyed(
- tileSpec,
- QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
- )
- qsTile.destroy()
- null
- }
- // Tile is in the current list of tiles and available.
- // We have a handful of different cases
- qsTile !is CustomTile -> {
- // The tile is not a custom tile. Make sure they are reset to the correct user
- if (userChanged) {
- qsTile.userSwitch(user)
- logger.logTileUserChanged(tileSpec, user)
+ return when (tileOrNotInstalled) {
+ is TileOrNotInstalled.NotInstalled -> null
+ is TileOrNotInstalled.Tile -> {
+ val qsTile = tileOrNotInstalled.tile
+ when {
+ !qsTile.isAvailable -> {
+ logger.logTileDestroyed(
+ tileSpec,
+ QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
+ )
+ qsTile.destroy()
+ null
+ }
+ // Tile is in the current list of tiles and available.
+ // We have a handful of different cases
+ qsTile !is CustomTile -> {
+ // The tile is not a custom tile. Make sure they are reset to the correct
+ // user
+ if (userChanged) {
+ qsTile.userSwitch(user)
+ logger.logTileUserChanged(tileSpec, user)
+ }
+ qsTile
+ }
+ qsTile.user == user -> {
+ // The tile is a custom tile for the same user, just return it
+ qsTile
+ }
+ else -> {
+ // The tile is a custom tile and the user has changed. Destroy it
+ qsTile.destroy()
+ logger.logTileDestroyed(
+ tileSpec,
+ QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED
+ )
+ null
+ }
}
- qsTile
- }
- qsTile.user == user -> {
- // The tile is a custom tile for the same user, just return it
- qsTile
- }
- else -> {
- // The tile is a custom tile and the user has changed. Destroy it
- qsTile.destroy()
- logger.logTileDestroyed(
- tileSpec,
- QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED
- )
- null
}
}
}
+
+ private sealed interface TileOrNotInstalled {
+ object NotInstalled : TileOrNotInstalled
+
+ @JvmInline value class Tile(val tile: QSTile) : TileOrNotInstalled
+ }
+
+ private data class UserAndTiles(
+ val userId: Int,
+ val tiles: List<TileSpec>,
+ )
+
+ private data class Data(
+ val oldData: UserAndTiles,
+ val newData: UserAndTiles,
+ val installedComponents: Set<ComponentName>,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index ff7d2068bc4e..8318ec99e530 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -124,6 +124,18 @@ constructor(
tileListLogBuffer.log(TILE_LIST_TAG, LogLevel.DEBUG, {}, { "Using retail tiles" })
}
+ fun logTilesNotInstalled(tiles: Collection<TileSpec>, user: Int) {
+ tileListLogBuffer.log(
+ TILE_LIST_TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = tiles.toString()
+ int1 = user
+ },
+ { "Tiles kept for not installed packages for user $int1: $str1" }
+ )
+ }
+
/** Reasons for destroying an existing tile. */
enum class TileDestroyedReason(val readable: String) {
TILE_REMOVED("Tile removed from current set"),
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index e1ac0fd1fd16..2c4555a2378a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -427,6 +427,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
Log.e(TAG, "stopRecording called, but there was an error when ending"
+ "recording");
exception.printStackTrace();
+ createErrorNotification();
} catch (Throwable throwable) {
// Something unexpected happen, SystemUI will crash but let's delete
// the temporary files anyway
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 3227ef47f733..bd1b7ca7916a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -137,7 +137,12 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// Since Quick Share target recommendation does not rely on image URL, it is
// queried and surfaced before image compress/export. Action intent would not be
// used, because it does not contain image URL.
- queryQuickShareAction(image, mParams.owner);
+ Notification.Action quickShare =
+ queryQuickShareAction(mScreenshotId, image, mParams.owner, null);
+ if (quickShare != null) {
+ mQuickShareData.quickShareAction = quickShare;
+ mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
+ }
}
// Call synchronously here since already on a background thread.
@@ -176,9 +181,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
smartActionsEnabled);
mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri,
smartActionsEnabled);
- mImageData.quickShareAction = createQuickShareAction(mContext,
- mQuickShareData.quickShareAction, uri);
- mImageData.subject = getSubjectString();
+ mImageData.quickShareAction = createQuickShareAction(
+ mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image,
+ mParams.owner);
+ mImageData.subject = getSubjectString(mImageTime);
mParams.mActionsReadyListener.onActionsReady(mImageData);
if (DEBUG_CALLBACK) {
@@ -251,7 +257,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
new ClipData.Item(uri));
sharingIntent.setClipData(clipdata);
- sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString());
+ sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString(mImageTime));
sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
@@ -417,60 +423,73 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
}
/**
- * Populate image uri into intent of Quick Share action.
+ * Wrap the quickshare intent and populate the fillin intent with the URI
*/
@VisibleForTesting
- private Notification.Action createQuickShareAction(Context context, Notification.Action action,
- Uri uri) {
- if (action == null) {
+ Notification.Action createQuickShareAction(
+ Notification.Action quickShare, String screenshotId, Uri uri, long imageTime,
+ Bitmap image, UserHandle user) {
+ if (quickShare == null) {
return null;
+ } else if (quickShare.actionIntent.isImmutable()) {
+ Notification.Action quickShareWithUri =
+ queryQuickShareAction(screenshotId, image, user, uri);
+ if (quickShareWithUri == null
+ || !quickShareWithUri.title.toString().contentEquals(quickShare.title)) {
+ return null;
+ }
+ quickShare = quickShareWithUri;
}
- // Populate image URI into Quick Share chip intent
- Intent sharingIntent = action.actionIntent.getIntent();
- sharingIntent.setType("image/png");
- sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
- String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
- String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
- sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
- // Include URI in ClipData also, so that grantPermission picks it up.
- // We don't use setData here because some apps interpret this as "to:".
- ClipData clipdata = new ClipData(new ClipDescription("content",
- new String[]{"image/png"}),
- new ClipData.Item(uri));
- sharingIntent.setClipData(clipdata);
- sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- PendingIntent updatedPendingIntent = PendingIntent.getActivity(
- context, 0, sharingIntent,
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-
- // Proxy smart actions through {@link SmartActionsReceiver} for logging smart actions.
- Bundle extras = action.getExtras();
+
+ Intent wrappedIntent = new Intent(mContext, SmartActionsReceiver.class)
+ .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent)
+ .putExtra(ScreenshotController.EXTRA_ACTION_INTENT_FILLIN,
+ createFillInIntent(uri, imageTime))
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ Bundle extras = quickShare.getExtras();
String actionType = extras.getString(
ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
- Intent intent = new Intent(context, SmartActionsReceiver.class)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, updatedPendingIntent)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
// We only query for quick share actions when smart actions are enabled, so we can assert
// that it's true here.
- addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
- PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
- mRandom.nextInt(),
- intent,
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- return new Notification.Action.Builder(action.getIcon(), action.title,
- broadcastIntent).setContextual(true).addExtras(extras).build();
+ addIntentExtras(screenshotId, wrappedIntent, actionType, true /* smartActionsEnabled */);
+ PendingIntent broadcastIntent =
+ PendingIntent.getBroadcast(mContext, mRandom.nextInt(), wrappedIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ return new Notification.Action.Builder(quickShare.getIcon(), quickShare.title,
+ broadcastIntent)
+ .setContextual(true)
+ .addExtras(extras)
+ .build();
+ }
+
+ private Intent createFillInIntent(Uri uri, long imageTime) {
+ Intent fillIn = new Intent();
+ fillIn.setType("image/png");
+ fillIn.putExtra(Intent.EXTRA_STREAM, uri);
+ fillIn.putExtra(Intent.EXTRA_SUBJECT, getSubjectString(imageTime));
+ // Include URI in ClipData also, so that grantPermission picks it up.
+ // We don't use setData here because some apps interpret this as "to:".
+ ClipData clipData = new ClipData(
+ new ClipDescription("content", new String[]{"image/png"}),
+ new ClipData.Item(uri));
+ fillIn.setClipData(clipData);
+ fillIn.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ return fillIn;
}
/**
* Query and surface Quick Share chip if it is available. Action intent would not be used,
* because it does not contain image URL which would be populated in {@link
- * #createQuickShareAction(Context, Notification.Action, Uri)}
+ * #createQuickShareAction(Notification.Action, String, Uri, long, Bitmap, UserHandle)}
*/
- private void queryQuickShareAction(Bitmap image, UserHandle user) {
+
+ @VisibleForTesting
+ Notification.Action queryQuickShareAction(
+ String screenshotId, Bitmap image, UserHandle user, Uri uri) {
CompletableFuture<List<Notification.Action>> quickShareActionsFuture =
mScreenshotSmartActions.getSmartActionsFuture(
- mScreenshotId, null, image, mSmartActionsProvider,
+ screenshotId, uri, image, mSmartActionsProvider,
ScreenshotSmartActionType.QUICK_SHARE_ACTION,
true /* smartActionsEnabled */, user);
int timeoutMs = DeviceConfig.getInt(
@@ -479,17 +498,17 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
500);
List<Notification.Action> quickShareActions =
mScreenshotSmartActions.getSmartActions(
- mScreenshotId, quickShareActionsFuture, timeoutMs,
+ screenshotId, quickShareActionsFuture, timeoutMs,
mSmartActionsProvider,
ScreenshotSmartActionType.QUICK_SHARE_ACTION);
if (!quickShareActions.isEmpty()) {
- mQuickShareData.quickShareAction = quickShareActions.get(0);
- mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
+ return quickShareActions.get(0);
}
+ return null;
}
- private String getSubjectString() {
- String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
+ private static String getSubjectString(long imageTime) {
+ String subjectDate = DateFormat.getDateTimeInstance().format(new Date(imageTime));
return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 77a65b22a7f4..b59106efb769 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -246,6 +246,7 @@ public class ScreenshotController {
static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
static final String EXTRA_OVERRIDE_TRANSITION = "android:screenshot_override_transition";
static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
+ static final String EXTRA_ACTION_INTENT_FILLIN = "android:screenshot_action_intent_fillin";
static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
index 68b46d2b7525..ca713feafe80 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
@@ -30,7 +30,6 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -61,7 +60,6 @@ public class ScreenshotSmartActions {
screenshotNotificationSmartActionsProviderProvider;
}
- @VisibleForTesting
CompletableFuture<List<Notification.Action>> getSmartActionsFuture(
String screenshotId, Uri screenshotUri, Bitmap image,
ScreenshotNotificationSmartActionsProvider smartActionsProvider,
@@ -83,7 +81,7 @@ public class ScreenshotSmartActions {
if (image.getConfig() != Bitmap.Config.HARDWARE) {
if (DEBUG_ACTIONS) {
Log.d(TAG, String.format("Bitmap expected: Hardware, Bitmap found: %s. "
- + "Returning empty list.", image.getConfig()));
+ + "Returning empty list.", image.getConfig()));
}
return CompletableFuture.completedFuture(Collections.emptyList());
}
@@ -112,7 +110,6 @@ public class ScreenshotSmartActions {
return smartActionsFuture;
}
- @VisibleForTesting
List<Notification.Action> getSmartActions(String screenshotId,
CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
ScreenshotNotificationSmartActionsProvider smartActionsProvider,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
index 45af1874e9db..9761f5931193 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
@@ -18,6 +18,7 @@ package com.android.systemui.screenshot;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS;
import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT;
+import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT_FILLIN;
import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_TYPE;
import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
@@ -46,7 +47,9 @@ public class SmartActionsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
+ PendingIntent pendingIntent =
+ intent.getParcelableExtra(EXTRA_ACTION_INTENT, PendingIntent.class);
+ Intent fillIn = intent.getParcelableExtra(EXTRA_ACTION_INTENT_FILLIN, Intent.class);
String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE);
if (DEBUG_ACTIONS) {
Log.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent());
@@ -54,7 +57,7 @@ public class SmartActionsReceiver extends BroadcastReceiver {
ActivityOptions opts = ActivityOptions.makeBasic();
try {
- pendingIntent.send(context, 0, null, null, null, null, opts.toBundle());
+ pendingIntent.send(context, 0, fillIn, null, null, null, opts.toBundle());
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Pending intent canceled", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ca8369950e4b..17a887019541 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -935,10 +935,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@Override
public void onUnlockAnimationStarted(
boolean playingCannedAnimation,
- boolean isWakeAndUnlock,
+ boolean isWakeAndUnlockNotFromDream,
long startDelay,
long unlockAnimationDuration) {
- unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay);
+ unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlockNotFromDream,
+ startDelay);
}
});
mAlternateBouncerInteractor = alternateBouncerInteractor;
@@ -953,7 +954,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private void unlockAnimationStarted(
boolean playingCannedAnimation,
- boolean isWakeAndUnlock,
+ boolean isWakeAndUnlockNotFromDream,
long unlockAnimationStartDelay) {
// Disable blurs while we're unlocking so that panel expansion does not
// cause blurring. This will eventually be re-enabled by the panel view on
@@ -961,7 +962,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
// unlock gesture, and we don't want that to cause blurring either.
mDepthController.setBlursDisabledForUnlock(mTracking);
- if (playingCannedAnimation && !isWakeAndUnlock) {
+ if (playingCannedAnimation && !isWakeAndUnlockNotFromDream) {
// Hide the panel so it's not in the way or the surface behind the
// keyguard, which will be appearing. If we're wake and unlocking, the
// lock screen is hidden instantly so should not be flung away.
@@ -2011,6 +2012,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
updateExpansionAndVisibility();
mNotificationStackScrollLayoutController.setPanelFlinging(false);
+ // expandImmediate should be always reset at the end of animation
+ mQsController.setExpandImmediate(false);
}
private boolean isInContentBounds(float x, float y) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index f080d3dfab1d..3af75cef3d4c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -34,6 +34,7 @@ import android.view.WindowInsets
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.core.view.doOnLayout
import com.android.app.animation.Interpolators
import com.android.settingslib.Utils
import com.android.systemui.Dumpable
@@ -220,6 +221,7 @@ constructor(
override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
override fun dispatchDemoCommand(command: String, args: Bundle) =
clock.dispatchDemoCommand(command, args)
+
override fun onDemoModeStarted() = clock.onDemoModeStarted()
override fun onDemoModeFinished() = clock.onDemoModeFinished()
}
@@ -259,6 +261,7 @@ constructor(
resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height)
lastInsets?.let { updateConstraintsForInsets(header, it) }
updateResources()
+ updateCarrierGroupPadding()
}
}
@@ -291,6 +294,7 @@ constructor(
privacyIconsController.chipVisibilityListener = chipVisibilityListener
updateVisibility()
updateTransition()
+ updateCarrierGroupPadding()
header.setOnApplyWindowInsetsListener(insetListener)
@@ -298,8 +302,6 @@ constructor(
val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
v.pivotX = newPivot
v.pivotY = v.height.toFloat() / 2
-
- mShadeCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0)
}
clock.setOnClickListener { launchClockActivity() }
@@ -359,6 +361,14 @@ constructor(
.load(context, resources.getXml(R.xml.large_screen_shade_header))
}
+ private fun updateCarrierGroupPadding() {
+ clock.doOnLayout {
+ val maxClockWidth =
+ (clock.width * resources.getFloat(R.dimen.qqs_expand_clock_scale)).toInt()
+ mShadeCarrierGroup.setPaddingRelative(maxClockWidth, 0, 0, 0)
+ }
+ }
+
private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) {
val cutout = insets.displayCutout.also { this.cutout = it }
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
index 641131e4dcc1..03be88fc31d9 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
@@ -34,6 +34,11 @@ abstract class SmartspaceModule {
const val DREAM_SMARTSPACE_DATA_PLUGIN = "dreams_smartspace_data_plugin"
/**
+ * The BcSmartspaceDataPlugin for the standalone weather on dream.
+ */
+ const val DREAM_WEATHER_SMARTSPACE_DATA_PLUGIN = "dream_weather_smartspace_data_plugin"
+
+ /**
* The dream smartspace target filter.
*/
const val DREAM_SMARTSPACE_TARGET_FILTER = "dream_smartspace_target_filter"
@@ -62,6 +67,10 @@ abstract class SmartspaceModule {
@Named(DREAM_SMARTSPACE_DATA_PLUGIN)
abstract fun optionalDreamsBcSmartspaceDataPlugin(): BcSmartspaceDataPlugin?
+ @BindsOptionalOf
+ @Named(DREAM_WEATHER_SMARTSPACE_DATA_PLUGIN)
+ abstract fun optionalDreamWeatherSmartspaceDataPlugin(): BcSmartspaceDataPlugin?
+
@Binds
@Named(DREAM_SMARTSPACE_PRECONDITION)
abstract fun bindSmartspacePrecondition(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index 0a18f2d89d87..56ea703668d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -188,7 +188,9 @@ constructor(
if (animationState.value == ANIMATING_OUT) {
coroutineScope.launch {
withTimeout(DISAPPEAR_ANIMATION_DURATION) {
- animationState.first { it == SHOWING_PERSISTENT_DOT || it == ANIMATION_QUEUED }
+ animationState.first {
+ it == SHOWING_PERSISTENT_DOT || it == IDLE || it == ANIMATION_QUEUED
+ }
notifyHidePersistentDot()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index bfb6fb0fcdc7..9f397fe9ac0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -556,6 +556,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public void onNotificationUpdated() {
+ if (mIsSummaryWithChildren) {
+ Trace.beginSection("ExpNotRow#onNotifUpdated (summary)");
+ } else {
+ Trace.beginSection("ExpNotRow#onNotifUpdated (leaf)");
+ }
for (NotificationContentView l : mLayouts) {
l.onNotificationUpdated(mEntry);
}
@@ -591,6 +596,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mUpdateSelfBackgroundOnUpdate = false;
updateBackgroundColorsOfSelf();
}
+ Trace.endSection();
}
private void updateBackgroundColorsOfSelf() {
@@ -2588,6 +2594,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mIsSummaryWithChildren = mChildrenContainer != null
&& mChildrenContainer.getNotificationChildCount() > 0;
if (mIsSummaryWithChildren) {
+ Trace.beginSection("ExpNotRow#onChildCountChanged (summary)");
NotificationViewWrapper wrapper = mChildrenContainer.getNotificationViewWrapper();
if (wrapper == null || wrapper.getNotificationHeader() == null) {
mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
@@ -2599,6 +2606,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
updateChildrenAppearance();
updateChildrenVisibility();
applyChildrenRoundness();
+ if (mIsSummaryWithChildren) {
+ Trace.endSection();
+ }
}
protected void expandNotification() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
index 77fd05186090..3e10f2ad52e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.res.Resources;
+import android.os.Trace;
import android.service.notification.StatusBarNotification;
import android.util.TypedValue;
import android.view.LayoutInflater;
@@ -57,6 +58,7 @@ public class HybridGroupManager {
}
private HybridNotificationView inflateHybridView(View contentView, ViewGroup parent) {
+ Trace.beginSection("HybridGroupManager#inflateHybridView");
LayoutInflater inflater = LayoutInflater.from(mContext);
int layout = contentView instanceof ConversationLayout
? R.layout.hybrid_conversation_notification
@@ -64,6 +66,7 @@ public class HybridGroupManager {
HybridNotificationView hybrid = (HybridNotificationView)
inflater.inflate(layout, parent, false);
parent.addView(hybrid);
+ Trace.endSection();
return hybrid;
}
@@ -90,12 +93,18 @@ public class HybridGroupManager {
public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
View contentView, StatusBarNotification notification,
ViewGroup parent) {
+ boolean isNewView = false;
if (reusableView == null) {
+ Trace.beginSection("HybridGroupManager#bindFromNotification");
reusableView = inflateHybridView(contentView, parent);
+ isNewView = true;
}
CharSequence titleText = resolveTitle(notification.getNotification());
CharSequence contentText = resolveText(notification.getNotification());
reusableView.bind(titleText, contentText, contentView);
+ if (isNewView) {
+ Trace.endSection();
+ }
return reusableView;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 451d837b63a0..124df8c3b815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -26,6 +26,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.RemoteException;
+import android.os.Trace;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.AttributeSet;
@@ -1193,6 +1194,7 @@ public class NotificationContentView extends FrameLayout implements Notification
private void updateSingleLineView() {
if (mIsChildInGroup) {
+ Trace.beginSection("NotifContentView#updateSingleLineView");
boolean isNewView = mSingleLineView == null;
mSingleLineView = mHybridGroupManager.bindFromNotification(
mSingleLineView, mContractedChild, mNotificationEntry.getSbn(), this);
@@ -1200,6 +1202,7 @@ public class NotificationContentView extends FrameLayout implements Notification
updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
mSingleLineView, mSingleLineView);
}
+ Trace.endSection();
} else if (mSingleLineView != null) {
removeView(mSingleLineView);
mSingleLineView = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index d18757d3453f..f8e374de11e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -379,6 +379,7 @@ public class NotificationChildrenContainer extends ViewGroup
}
public void recreateNotificationHeader(OnClickListener listener, boolean isConversation) {
+ Trace.beginSection("NotifChildCont#recreateHeader");
mHeaderClickListener = listener;
mIsConversation = isConversation;
StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
@@ -406,6 +407,7 @@ public class NotificationChildrenContainer extends ViewGroup
recreateLowPriorityHeader(builder, isConversation);
updateHeaderVisibility(false /* animate */);
updateChildrenAppearance();
+ Trace.endSection();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 64fcfd867429..f7c6594e3a70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -4406,6 +4406,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
boolean nowHiddenAtAll = mAmbientState.isHiddenAtAll();
if (nowFullyHidden != wasFullyHidden) {
updateVisibility();
+ mSwipeHelper.resetTouchState();
}
if (!wasHiddenAtAll && nowHiddenAtAll) {
resetExposedMenuView(true /* animate */, true /* animate */);
@@ -4700,6 +4701,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) {
mController.updateShowEmptyShadeView();
updateFooter();
+ mController.updateImportantForAccessibility();
}
updateSpeedBumpIndex();
@@ -4711,6 +4713,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
mController.updateShowEmptyShadeView();
updateFooter();
+ mController.updateImportantForAccessibility();
}
updateSpeedBumpIndex();
@@ -4723,6 +4726,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
mController.updateShowEmptyShadeView();
updateFooter();
+ mController.updateImportantForAccessibility();
}
updateSpeedBumpIndex();
@@ -5863,7 +5867,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
private void cancelActiveSwipe() {
- mSwipeHelper.resetSwipeState();
+ mSwipeHelper.resetTouchState();
updateContinuousShadowDrawing();
}
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 7b046d6c9256..9272c376d4fe 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
@@ -327,6 +327,7 @@ public class NotificationStackScrollLayoutController {
mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(),
mLockscreenUserManager.isAnyProfilePublicMode());
mView.onStatePostChange(mStatusBarStateController.fromShadeLocked());
+ updateImportantForAccessibility();
}
};
@@ -1205,6 +1206,7 @@ public class NotificationStackScrollLayoutController {
if (mView.getVisibility() == View.VISIBLE) {
// Synchronize EmptyShadeView visibility with the parent container.
updateShowEmptyShadeView();
+ updateImportantForAccessibility();
}
}
@@ -1232,6 +1234,22 @@ public class NotificationStackScrollLayoutController {
}
/**
+ * Update the importantForAccessibility of NotificationStackScrollLayout.
+ * <p>
+ * We want the NSSL to be unimportant for accessibility when there's no
+ * notifications in it while the device is on lock screen, to avoid unlablel NSSL view.
+ * Otherwise, we want it to be important for accessibility to enable accessibility
+ * auto-scrolling in NSSL.
+ */
+ public void updateImportantForAccessibility() {
+ if (getVisibleNotificationCount() == 0 && mView.onKeyguard()) {
+ mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ } else {
+ mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+ }
+
+ /**
* @return true if {@link StatusBarStateController} is in transition to the KEYGUARD
* and false otherwise.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index d585163aa223..02b7e9176cf2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -21,7 +21,6 @@ import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_STANDARD;
import android.app.ActivityManager;
-import android.app.Notification;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
@@ -47,7 +46,6 @@ import android.view.OnReceiveContentListener;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
-import android.view.ViewGroupOverlay;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
@@ -58,6 +56,7 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
+import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -436,25 +435,23 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
if (animate && parent != null && mIsFocusAnimationFlagActive) {
ViewGroup grandParent = (ViewGroup) parent.getParent();
- ViewGroupOverlay overlay = parent.getOverlay();
View actionsContainer = getActionsContainerLayout();
int actionsContainerHeight =
actionsContainer != null ? actionsContainer.getHeight() : 0;
- // After adding this RemoteInputView to the overlay of the parent (and thus removing
- // it from the parent itself), the parent will shrink in height. This causes the
- // overlay to be moved. To correct the position of the overlay we need to offset it.
- int overlayOffsetY = actionsContainerHeight - getHeight();
- overlay.add(this);
+ // When defocusing, the notification needs to shrink. Therefore, we need to free
+ // up the space that was needed for the RemoteInputView. This is done by setting
+ // a negative top margin of the height difference of the RemoteInputView and its
+ // sibling (the actions_container_layout containing the Reply button etc.)
+ final int heightToShrink = actionsContainerHeight - getHeight();
+ setTopMargin(heightToShrink);
if (grandParent != null) grandParent.setClipChildren(false);
- Animator animator = getDefocusAnimator(actionsContainer, overlayOffsetY);
- View self = this;
+ final Animator animator = getDefocusAnimator(actionsContainer);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- overlay.remove(self);
- parent.addView(self);
+ setTopMargin(0);
if (grandParent != null) grandParent.setClipChildren(true);
setVisibility(GONE);
if (mWrapper != null) {
@@ -499,6 +496,13 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
}
}
+ private void setTopMargin(int topMargin) {
+ if (!(getLayoutParams() instanceof FrameLayout.LayoutParams)) return;
+ final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
+ layoutParams.topMargin = topMargin;
+ setLayoutParams(layoutParams);
+ }
+
@VisibleForTesting
protected void setViewRootImpl(ViewRootImpl viewRoot) {
mTestableViewRootImpl = viewRoot;
@@ -674,12 +678,12 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mProgressBar.setVisibility(INVISIBLE);
mResetting = true;
mSending = false;
+ mController.removeSpinning(mEntry.getKey(), mToken);
onDefocus(true /* animate */, false /* logClose */, () -> {
mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
mEditText.getText().clear();
mEditText.setEnabled(isAggregatedVisible());
mSendButton.setVisibility(VISIBLE);
- mController.removeSpinning(mEntry.getKey(), mToken);
updateSendButton();
setAttachment(null);
mResetting = false;
@@ -874,7 +878,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
ValueAnimator scaleAnimator = ValueAnimator.ofFloat(FOCUS_ANIMATION_MIN_SCALE, 1f);
scaleAnimator.addUpdateListener(valueAnimator -> {
- setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue(), 0);
+ setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
});
scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
@@ -901,9 +905,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
* Creates an animator for the defocus animation.
*
* @param fadeInView View that will be faded in during the defocus animation.
- * @param offsetY The RemoteInputView will be offset by offsetY during the animation
*/
- private Animator getDefocusAnimator(@Nullable View fadeInView, int offsetY) {
+ private Animator getDefocusAnimator(@Nullable View fadeInView) {
final AnimatorSet animatorSet = new AnimatorSet();
final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f);
@@ -913,14 +916,14 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1f, FOCUS_ANIMATION_MIN_SCALE);
scaleAnimator.addUpdateListener(valueAnimator -> {
- setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue(), offsetY);
+ setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
});
scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
scaleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
- setFocusAnimationScaleY(1f /* scaleY */, 0 /* verticalOffset */);
+ setFocusAnimationScaleY(1f /* scaleY */);
}
});
@@ -942,9 +945,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
* Sets affected view properties for a vertical scale animation
*
* @param scaleY desired vertical view scale
- * @param verticalOffset vertical offset to apply to the RemoteInputView during the animation
*/
- private void setFocusAnimationScaleY(float scaleY, int verticalOffset) {
+ private void setFocusAnimationScaleY(float scaleY) {
int verticalBoundOffset = (int) ((1f - scaleY) * 0.5f * mContentView.getHeight());
Rect contentBackgroundBounds = new Rect(0, verticalBoundOffset, mContentView.getWidth(),
mContentView.getHeight() - verticalBoundOffset);
@@ -955,7 +957,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
} else {
mContentBackgroundBounds = contentBackgroundBounds;
}
- setTranslationY(verticalBoundOffset + verticalOffset);
+ setTranslationY(verticalBoundOffset);
}
/** Handler for button click on send action in IME. */
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index 7236e0fd134a..59f2cdb745ca 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -224,7 +224,7 @@ object UserSwitcherViewBinder {
}
sectionView.removeAllViewsInLayout()
- for (viewModel in section) {
+ section.onEachIndexed { index, viewModel ->
val view =
layoutInflater.inflate(
R.layout.user_switcher_fullscreen_popup_item,
@@ -237,6 +237,13 @@ object UserSwitcherViewBinder {
view.resources.getString(viewModel.textResourceId)
view.setOnClickListener { viewModel.onClicked() }
sectionView.addView(view)
+ // Ensure that the first item in the first section gets accessibility focus.
+ // Request for focus with a delay when view is inflated an added to the listview.
+ if (index == 0 && position == 0) {
+ view.postDelayed({
+ view.requestAccessibilityFocus()
+ }, 200)
+ }
}
return sectionView
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
new file mode 100644
index 000000000000..891ee0cf66d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
@@ -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.util.kotlin
+
+import android.annotation.WorkerThread
+import android.content.pm.ComponentInfo
+import android.content.pm.PackageManager
+import com.android.systemui.util.Assert
+
+@WorkerThread
+fun PackageManager.isComponentActuallyEnabled(componentInfo: ComponentInfo): Boolean {
+ Assert.isNotMainThread()
+ return when (getComponentEnabledSetting(componentInfo.componentName)) {
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> false
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> componentInfo.isEnabled
+ else -> false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index b24a69292186..b848d2e84faf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -2006,14 +2006,14 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
if (row.anim == null) {
row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
row.anim.setInterpolator(new DecelerateInterpolator());
+ row.anim.addListener(
+ getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION));
} else {
row.anim.cancel();
row.anim.setIntValues(progress, newProgress);
}
row.animTargetProgress = newProgress;
row.anim.setDuration(UPDATE_ANIMATION_DURATION);
- row.anim.addListener(
- getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION));
row.anim.start();
} else {
// update slider directly to clamped value
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
index 14ad3acf7fb0..263d3750c657 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
@@ -26,18 +26,17 @@ import android.text.TextPaint
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
+import kotlin.math.ceil
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.eq
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-
-import kotlin.math.ceil
+import org.mockito.Mockito.`when`
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -56,15 +55,13 @@ class TextAnimatorTest : SysuiTestCase() {
val paint = mock(TextPaint::class.java)
`when`(textInterpolator.targetPaint).thenReturn(paint)
- val textAnimator = TextAnimator(layout, {}).apply {
- this.textInterpolator = textInterpolator
- this.animator = valueAnimator
- }
+ val textAnimator =
+ TextAnimator(layout, null, {}).apply {
+ this.textInterpolator = textInterpolator
+ this.animator = valueAnimator
+ }
- textAnimator.setTextStyle(
- weight = 400,
- animate = true
- )
+ textAnimator.setTextStyle(weight = 400, animate = true)
// If animation is requested, the base state should be rebased and the target state should
// be updated.
@@ -88,15 +85,13 @@ class TextAnimatorTest : SysuiTestCase() {
val paint = mock(TextPaint::class.java)
`when`(textInterpolator.targetPaint).thenReturn(paint)
- val textAnimator = TextAnimator(layout, {}).apply {
- this.textInterpolator = textInterpolator
- this.animator = valueAnimator
- }
+ val textAnimator =
+ TextAnimator(layout, null, {}).apply {
+ this.textInterpolator = textInterpolator
+ this.animator = valueAnimator
+ }
- textAnimator.setTextStyle(
- weight = 400,
- animate = false
- )
+ textAnimator.setTextStyle(weight = 400, animate = false)
// If animation is not requested, the progress should be 1 which is end of animation and the
// base state is rebased to target state by calling rebase.
@@ -118,15 +113,16 @@ class TextAnimatorTest : SysuiTestCase() {
`when`(textInterpolator.targetPaint).thenReturn(paint)
val animationEndCallback = mock(Runnable::class.java)
- val textAnimator = TextAnimator(layout, {}).apply {
- this.textInterpolator = textInterpolator
- this.animator = valueAnimator
- }
+ val textAnimator =
+ TextAnimator(layout, null, {}).apply {
+ this.textInterpolator = textInterpolator
+ this.animator = valueAnimator
+ }
textAnimator.setTextStyle(
- weight = 400,
- animate = true,
- onAnimationEnd = animationEndCallback
+ weight = 400,
+ animate = true,
+ onAnimationEnd = animationEndCallback
)
// Verify animationEnd callback has been added.
@@ -144,34 +140,27 @@ class TextAnimatorTest : SysuiTestCase() {
val layout = makeLayout("Hello, World", PAINT)
val valueAnimator = mock(ValueAnimator::class.java)
val textInterpolator = mock(TextInterpolator::class.java)
- val paint = TextPaint().apply {
- typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf")
- }
+ val paint =
+ TextPaint().apply {
+ typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf")
+ }
`when`(textInterpolator.targetPaint).thenReturn(paint)
- val textAnimator = TextAnimator(layout, {}).apply {
- this.textInterpolator = textInterpolator
- this.animator = valueAnimator
- }
+ val textAnimator =
+ TextAnimator(layout, null, {}).apply {
+ this.textInterpolator = textInterpolator
+ this.animator = valueAnimator
+ }
- textAnimator.setTextStyle(
- weight = 400,
- animate = true
- )
+ textAnimator.setTextStyle(weight = 400, animate = true)
val prevTypeface = paint.typeface
- textAnimator.setTextStyle(
- weight = 700,
- animate = true
- )
+ textAnimator.setTextStyle(weight = 700, animate = true)
assertThat(paint.typeface).isNotSameInstanceAs(prevTypeface)
- textAnimator.setTextStyle(
- weight = 400,
- animate = true
- )
+ textAnimator.setTextStyle(weight = 400, animate = true)
assertThat(paint.typeface).isSameInstanceAs(prevTypeface)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 477e076669b7..22308414547a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -1,6 +1,7 @@
package com.android.systemui.keyguard
import android.app.ActivityManager
+import android.app.WallpaperManager
import android.app.WindowConfiguration
import android.graphics.Point
import android.graphics.Rect
@@ -21,6 +22,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argThat
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertEquals
@@ -32,6 +34,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
@@ -64,6 +67,8 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock
private lateinit var powerManager: PowerManager
+ @Mock
+ private lateinit var wallpaperManager: WallpaperManager
@Mock
private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController.Stub
@@ -94,13 +99,14 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
context, keyguardStateController, { keyguardViewMediator }, keyguardViewController,
featureFlags, { biometricUnlockController }, statusBarStateController,
- notificationShadeWindowController, powerManager
+ notificationShadeWindowController, powerManager, wallpaperManager
)
keyguardUnlockAnimationController.setLauncherUnlockController(
launcherUnlockAnimationController)
whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
whenever(powerManager.isInteractive).thenReturn(true)
+ whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false)
// All of these fields are final, so we can't mock them, but are needed so that the surface
// appear amount setter doesn't short circuit.
@@ -173,6 +179,46 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
false /* cancelled */)
}
+ @Test
+ fun onWakeAndUnlock_notifiesListenerWithTrue() {
+ whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
+ whenever(biometricUnlockController.mode).thenReturn(
+ BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+
+ val listener = mock(
+ KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
+ keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
+
+ keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
+ remoteAnimationTargets,
+ wallpaperTargets,
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
+ )
+
+ verify(listener).onUnlockAnimationStarted(any(), eq(true), any(), any())
+ }
+
+ @Test
+ fun onWakeAndUnlockFromDream_notifiesListenerWithFalse() {
+ whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
+ whenever(biometricUnlockController.mode).thenReturn(
+ BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+
+ val listener = mock(
+ KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
+ keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
+
+ keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
+ remoteAnimationTargets,
+ wallpaperTargets,
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
+ )
+
+ verify(listener).onUnlockAnimationStarted(any(), eq(false), any(), any())
+ }
+
/**
* If we requested that the surface behind be made visible, and we're not flinging away the
* keyguard, it means that we're swiping to unlock and want the surface visible so it can follow
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 111b8e83a984..d36e77889810 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -92,8 +92,8 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
}
@Test
- fun affordance_walletNotEnabled_modelIsNone() = runBlockingTest {
- setUpState(isWalletEnabled = false)
+ fun affordance_walletFeatureNotEnabled_modelIsNone() = runBlockingTest {
+ setUpState(isWalletFeatureAvailable = false)
var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
@@ -165,7 +165,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = runTest {
setUpState(
- isWalletEnabled = false,
+ isWalletFeatureAvailable = false,
)
assertThat(underTest.getPickerScreenState())
@@ -183,16 +183,15 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
}
private fun setUpState(
- isWalletEnabled: Boolean = true,
+ isWalletFeatureAvailable: Boolean = true,
isWalletServiceAvailable: Boolean = true,
isWalletQuerySuccessful: Boolean = true,
hasSelectedCard: Boolean = true,
) {
- whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled)
-
val walletClient: QuickAccessWalletClient = mock()
whenever(walletClient.tileIcon).thenReturn(ICON)
whenever(walletClient.isWalletServiceAvailable).thenReturn(isWalletServiceAvailable)
+ whenever(walletClient.isWalletFeatureAvailable).thenReturn(isWalletFeatureAvailable)
whenever(walletController.walletClient).thenReturn(walletClient)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index 705b485ce1b4..9dba9b5b3c3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.media.dialog;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.mock;
@@ -33,6 +35,10 @@ import android.media.session.MediaSessionManager;
import android.os.PowerExemptionManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
import androidx.test.filters.SmallTest;
@@ -44,6 +50,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.media.BluetoothMediaDevice;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
@@ -53,11 +60,14 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.google.common.base.Strings;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -68,6 +78,9 @@ import java.util.Optional;
public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
private static final String TEST_PACKAGE = "test_package";
+ private static final String BROADCAST_NAME_TEST = "Broadcast_name_test";
+ private static final String BROADCAST_CODE_TEST = "112233";
+ private static final String BROADCAST_CODE_UPDATE_TEST = "11223344";
// Mock
private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
@@ -106,6 +119,9 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(null);
+ when(mLocalBluetoothLeBroadcast.getProgramInfo()).thenReturn(BROADCAST_NAME_TEST);
+ when(mLocalBluetoothLeBroadcast.getBroadcastCode()).thenReturn(
+ BROADCAST_CODE_TEST.getBytes(StandardCharsets.UTF_8));
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
@@ -194,4 +210,152 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
verify(mLocalBluetoothLeBroadcastAssistant, times(1)).addSource(any(), any(), anyBoolean());
}
+
+ @Test
+ public void handleLeBroadcastMetadataChanged_checkBroadcastName() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ final TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_name_summary);
+
+ mMediaOutputBroadcastDialog.handleLeBroadcastMetadataChanged();
+
+ assertThat(broadcastName.getText().toString()).isEqualTo(BROADCAST_NAME_TEST);
+ }
+
+ @Test
+ public void handleLeBroadcastMetadataChanged_checkBroadcastCode() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+
+ final TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_code_summary);
+
+ mMediaOutputBroadcastDialog.handleLeBroadcastMetadataChanged();
+
+ assertThat(broadcastCode.getText().toString()).isEqualTo(BROADCAST_CODE_TEST);
+ }
+
+ @Test
+ public void updateBroadcastInfo_stopBroadcastFailed_handleFailedUi() {
+ ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_code_edit);
+ TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_code_summary);
+ broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
+ broadcastCodeEdit.callOnClick();
+
+ mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
+ assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(1);
+
+ mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
+ assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(2);
+
+ // It will be the MAX Retry Count = 3
+ mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
+ assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void afterTextChanged_nameLengthMoreThanMax_showErrorMessage() {
+ ImageView broadcastNameEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_name_edit);
+ TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_name_summary);
+ broadcastName.setText(BROADCAST_NAME_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastNameEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String moreThanMax = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH + 3);
+ editText.setText(moreThanMax);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void afterTextChanged_enterValidNameAfterLengthMoreThanMax_noErrorMessage() {
+ ImageView broadcastNameEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_name_edit);
+ TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_name_summary);
+ broadcastName.setText(BROADCAST_NAME_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastNameEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String testString = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH + 2);
+ editText.setText(testString);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+
+ // input the valid text
+ testString = Strings.repeat("b",
+ MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH - 100);
+ editText.setText(testString);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+ }
+
+ @Test
+ public void afterTextChanged_codeLengthMoreThanMax_showErrorMessage() {
+ ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_code_edit);
+ TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_code_summary);
+ broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastCodeEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String moreThanMax = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_CODE_MAX_LENGTH + 1);
+ editText.setText(moreThanMax);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void afterTextChanged_codeLengthLessThanMin_showErrorMessage() {
+ ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_code_edit);
+ TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_code_summary);
+ broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastCodeEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String moreThanMax = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_CODE_MIN_LENGTH - 1);
+ editText.setText(moreThanMax);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
new file mode 100644
index 000000000000..18f3837a7d36
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -0,0 +1,278 @@
+/*
+ * 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.qs.pipeline.data.repository
+
+import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.os.UserHandle
+import android.service.quicksettings.TileService
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var packageManager: PackageManager
+ @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+
+ private lateinit var underTest: InstalledTilesComponentRepositoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ // Use the default value set in the ServiceInfo
+ whenever(packageManager.getComponentEnabledSetting(any()))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+ // Return empty by default
+ whenever(packageManager.queryIntentServicesAsUser(any(), any<ResolveInfoFlags>(), anyInt()))
+ .thenReturn(emptyList())
+
+ underTest =
+ InstalledTilesComponentRepositoryImpl(
+ context,
+ packageManager,
+ testDispatcher,
+ )
+ }
+
+ @Test
+ fun registersAndUnregistersBroadcastReceiver() =
+ testScope.runTest {
+ val user = 10
+ val job = launch { underTest.getInstalledTilesComponents(user).collect {} }
+ runCurrent()
+
+ verify(context)
+ .registerReceiverAsUser(
+ capture(receiverCaptor),
+ eq(UserHandle.of(user)),
+ any(),
+ nullable(),
+ nullable(),
+ )
+
+ verify(context, never()).unregisterReceiver(receiverCaptor.value)
+
+ job.cancel()
+ runCurrent()
+ verify(context).unregisterReceiver(receiverCaptor.value)
+ }
+
+ @Test
+ fun intentFilterForCorrectActionsAndScheme() =
+ testScope.runTest {
+ val filterCaptor = argumentCaptor<IntentFilter>()
+
+ backgroundScope.launch { underTest.getInstalledTilesComponents(0).collect {} }
+ runCurrent()
+
+ verify(context)
+ .registerReceiverAsUser(
+ any(),
+ any(),
+ capture(filterCaptor),
+ nullable(),
+ nullable(),
+ )
+
+ with(filterCaptor.value) {
+ assertThat(matchAction(Intent.ACTION_PACKAGE_CHANGED)).isTrue()
+ assertThat(matchAction(Intent.ACTION_PACKAGE_ADDED)).isTrue()
+ assertThat(matchAction(Intent.ACTION_PACKAGE_REMOVED)).isTrue()
+ assertThat(matchAction(Intent.ACTION_PACKAGE_REPLACED)).isTrue()
+ assertThat(countActions()).isEqualTo(4)
+
+ assertThat(hasDataScheme("package")).isTrue()
+ assertThat(countDataSchemes()).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun componentsLoadedOnStart() =
+ testScope.runTest {
+ val userId = 0
+ val resolveInfo =
+ ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
+ whenever(
+ packageManager.queryIntentServicesAsUser(
+ matchIntent(),
+ matchFlags(),
+ eq(userId)
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+
+ val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+
+ assertThat(componentNames).containsExactly(TEST_COMPONENT)
+ }
+
+ @Test
+ fun componentAdded_foundAfterBroadcast() =
+ testScope.runTest {
+ val userId = 0
+ val resolveInfo =
+ ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
+
+ val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ assertThat(componentNames).isEmpty()
+
+ whenever(
+ packageManager.queryIntentServicesAsUser(
+ matchIntent(),
+ matchFlags(),
+ eq(userId)
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+ getRegisteredReceiver().onReceive(context, Intent(Intent.ACTION_PACKAGE_ADDED))
+
+ assertThat(componentNames).containsExactly(TEST_COMPONENT)
+ }
+
+ @Test
+ fun componentWithoutPermission_notValid() =
+ testScope.runTest {
+ val userId = 0
+ val resolveInfo =
+ ResolveInfo(TEST_COMPONENT, hasPermission = false, defaultEnabled = true)
+ whenever(
+ packageManager.queryIntentServicesAsUser(
+ matchIntent(),
+ matchFlags(),
+ eq(userId)
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+
+ val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ assertThat(componentNames).isEmpty()
+ }
+
+ @Test
+ fun componentNotEnabled_notValid() =
+ testScope.runTest {
+ val userId = 0
+ val resolveInfo =
+ ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = false)
+ whenever(
+ packageManager.queryIntentServicesAsUser(
+ matchIntent(),
+ matchFlags(),
+ eq(userId)
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+
+ val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ assertThat(componentNames).isEmpty()
+ }
+
+ private fun getRegisteredReceiver(): BroadcastReceiver {
+ verify(context)
+ .registerReceiverAsUser(
+ capture(receiverCaptor),
+ any(),
+ any(),
+ nullable(),
+ nullable(),
+ )
+
+ return receiverCaptor.value
+ }
+
+ companion object {
+ private val INTENT = Intent(TileService.ACTION_QS_TILE)
+ private val FLAGS =
+ ResolveInfoFlags.of(
+ (PackageManager.MATCH_DIRECT_BOOT_AWARE or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
+ PackageManager.GET_SERVICES)
+ .toLong()
+ )
+ private val PERMISSION = BIND_QUICK_SETTINGS_TILE
+
+ private val TEST_COMPONENT = ComponentName("pkg", "cls")
+
+ private fun matchFlags() =
+ argThat<ResolveInfoFlags> { flags -> flags?.value == FLAGS.value }
+ private fun matchIntent() = argThat<Intent> { intent -> intent.action == INTENT.action }
+
+ private fun ResolveInfo(
+ componentName: ComponentName,
+ hasPermission: Boolean,
+ defaultEnabled: Boolean
+ ): ResolveInfo {
+ val applicationInfo = ApplicationInfo().apply { enabled = true }
+ val serviceInfo =
+ ServiceInfo().apply {
+ packageName = componentName.packageName
+ name = componentName.className
+ if (hasPermission) {
+ permission = PERMISSION
+ }
+ enabled = defaultEnabled
+ this.applicationInfo = applicationInfo
+ }
+ val resolveInfo = ResolveInfo()
+ resolveInfo.serviceInfo = serviceInfo
+ return resolveInfo
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 426ff670802f..e7ad4896810b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -38,6 +38,7 @@ import com.android.systemui.qs.external.TileLifecycleManager
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
import com.android.systemui.qs.pipeline.data.repository.FakeCustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.domain.model.TileModel
@@ -73,6 +74,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
private val tileSpecRepository: TileSpecRepository = FakeTileSpecRepository()
private val userRepository = FakeUserRepository()
+ private val installedTilesPackageRepository = FakeInstalledTilesComponentRepository()
private val tileFactory = FakeQSFactory(::tileCreator)
private val customTileAddedRepository: CustomTileAddedRepository =
FakeCustomTileAddedRepository()
@@ -100,11 +102,13 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
+
setUserTracker(0)
underTest =
CurrentTilesInteractorImpl(
tileSpecRepository = tileSpecRepository,
+ installedTilesComponentRepository = installedTilesPackageRepository,
userRepository = userRepository,
customTileStatePersister = customTileStatePersister,
tileFactory = tileFactory,
@@ -609,6 +613,40 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
assertThat((tileA as FakeQSTile).callbacks).containsExactly(callback)
}
+ @Test
+ fun packageNotInstalled_customTileNotVisible() =
+ testScope.runTest(USER_INFO_0) {
+ installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
+
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+ assertThat(tiles!!.size).isEqualTo(1)
+ assertThat(tiles!![0].spec).isEqualTo(specs[0])
+ }
+
+ @Test
+ fun packageInstalledLater_customTileAdded() =
+ testScope.runTest(USER_INFO_0) {
+ installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
+
+ val tiles by collectLastValue(underTest.currentTiles)
+ val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC, TileSpec.create("b"))
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+ assertThat(tiles!!.size).isEqualTo(2)
+
+ installedTilesPackageRepository.setInstalledPackagesForUser(
+ USER_INFO_0.id,
+ setOf(TEST_COMPONENT)
+ )
+
+ assertThat(tiles!!.size).isEqualTo(3)
+ assertThat(tiles!![1].spec).isEqualTo(CUSTOM_TILE_SPEC)
+ }
+
private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
this.state = state
this.label = label
@@ -654,6 +692,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
private suspend fun switchUser(user: UserInfo) {
setUserTracker(user.id)
+ installedTilesPackageRepository.setInstalledPackagesForUser(user.id, setOf(TEST_COMPONENT))
userRepository.setSelectedUserInfo(user)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 3def6ba1fc58..8744aa346f8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -204,6 +204,17 @@ public class RecordingServiceTest extends SysuiTestCase {
}
@Test
+ public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_showsErrorNotification()
+ throws IOException {
+ doReturn(true).when(mController).isRecording();
+ doThrow(new RuntimeException()).when(mScreenMediaRecorder).end();
+
+ mRecordingService.onStopped();
+
+ verify(mRecordingService).createErrorNotification();
+ }
+
+ @Test
public void testOnSystemRequestedStop_recorderEndThrowsOOMError_releasesRecording()
throws IOException {
doReturn(true).when(mController).isRecording();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
new file mode 100644
index 000000000000..fbb77cdc3049
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
@@ -0,0 +1,283 @@
+/*
+ * 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.screenshot
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.net.Uri
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.screenshot.ScreenshotController.SaveImageInBackgroundData
+import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import java.util.concurrent.CompletableFuture
+import java.util.function.Supplier
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class SaveImageInBackgroundTaskTest : SysuiTestCase() {
+ private val imageExporter = mock<ImageExporter>()
+ private val smartActions = mock<ScreenshotSmartActions>()
+ private val smartActionsProvider = mock<ScreenshotNotificationSmartActionsProvider>()
+ private val saveImageData = SaveImageInBackgroundData()
+ private val sharedTransitionSupplier =
+ mock<Supplier<ScreenshotController.SavedImageData.ActionTransition>>()
+ private val testScreenshotId: String = "testScreenshotId"
+ private val testBitmap = mock<Bitmap>()
+ private val testUser = UserHandle.getUserHandleForUid(0)
+ private val testIcon = mock<Icon>()
+ private val testImageTime = 1234.toLong()
+ private val flags = FakeFeatureFlags()
+
+ private val smartActionsUriFuture = mock<CompletableFuture<List<Notification.Action>>>()
+ private val smartActionsFuture = mock<CompletableFuture<List<Notification.Action>>>()
+
+ private val testUri: Uri = Uri.parse("testUri")
+ private val intent =
+ Intent(Intent.ACTION_SEND)
+ .setComponent(
+ ComponentName.unflattenFromString(
+ "com.google.android.test/com.google.android.test.TestActivity"
+ )
+ )
+ private val immutablePendingIntent =
+ PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ private val mutablePendingIntent =
+ PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
+
+ private val saveImageTask =
+ SaveImageInBackgroundTask(
+ mContext,
+ flags,
+ imageExporter,
+ smartActions,
+ saveImageData,
+ sharedTransitionSupplier,
+ smartActionsProvider,
+ )
+
+ @Before
+ fun setup() {
+ whenever(
+ smartActions.getSmartActionsFuture(
+ eq(testScreenshotId),
+ any(Uri::class.java),
+ eq(testBitmap),
+ eq(smartActionsProvider),
+ any(ScreenshotSmartActionType::class.java),
+ any(Boolean::class.java),
+ eq(testUser)
+ )
+ )
+ .thenReturn(smartActionsUriFuture)
+ whenever(
+ smartActions.getSmartActionsFuture(
+ eq(testScreenshotId),
+ eq(null),
+ eq(testBitmap),
+ eq(smartActionsProvider),
+ any(ScreenshotSmartActionType::class.java),
+ any(Boolean::class.java),
+ eq(testUser)
+ )
+ )
+ .thenReturn(smartActionsFuture)
+ }
+
+ @Test
+ fun testQueryQuickShare_noAction() {
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(ArrayList<Notification.Action>())
+
+ val quickShareAction =
+ saveImageTask.queryQuickShareAction(testScreenshotId, testBitmap, testUser, testUri)
+
+ assertNull(quickShareAction)
+ }
+
+ @Test
+ fun testQueryQuickShare_withActions() {
+ val actions = ArrayList<Notification.Action>()
+ actions.add(constructAction("Action One", mutablePendingIntent))
+ actions.add(constructAction("Action Two", mutablePendingIntent))
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsUriFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(actions)
+
+ val quickShareAction =
+ saveImageTask.queryQuickShareAction(testScreenshotId, testBitmap, testUser, testUri)!!
+
+ assertEquals("Action One", quickShareAction.title)
+ assertEquals(mutablePendingIntent, quickShareAction.actionIntent)
+ }
+
+ @Test
+ fun testCreateQuickShareAction_originalWasNull_returnsNull() {
+ val quickShareAction =
+ saveImageTask.createQuickShareAction(
+ null,
+ testScreenshotId,
+ testUri,
+ testImageTime,
+ testBitmap,
+ testUser
+ )
+
+ assertNull(quickShareAction)
+ }
+
+ @Test
+ fun testCreateQuickShareAction_immutableIntentDifferentAction_returnsNull() {
+ val actions = ArrayList<Notification.Action>()
+ actions.add(constructAction("New Test Action", immutablePendingIntent))
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsUriFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(actions)
+ val origAction = constructAction("Old Test Action", immutablePendingIntent)
+
+ val quickShareAction =
+ saveImageTask.createQuickShareAction(
+ origAction,
+ testScreenshotId,
+ testUri,
+ testImageTime,
+ testBitmap,
+ testUser,
+ )
+
+ assertNull(quickShareAction)
+ }
+
+ @Test
+ fun testCreateQuickShareAction_mutableIntent_returnsSafeIntent() {
+ val actions = ArrayList<Notification.Action>()
+ val action = constructAction("Action One", mutablePendingIntent)
+ actions.add(action)
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsUriFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(actions)
+
+ val quickShareAction =
+ saveImageTask.createQuickShareAction(
+ constructAction("Test Action", mutablePendingIntent),
+ testScreenshotId,
+ testUri,
+ testImageTime,
+ testBitmap,
+ testUser
+ )
+ val quickSharePendingIntent =
+ quickShareAction.actionIntent.intent.extras!!.getParcelable(
+ ScreenshotController.EXTRA_ACTION_INTENT,
+ PendingIntent::class.java
+ )
+
+ assertEquals("Test Action", quickShareAction.title)
+ assertEquals(mutablePendingIntent, quickSharePendingIntent)
+ }
+
+ @Test
+ fun testCreateQuickShareAction_immutableIntent_returnsSafeIntent() {
+ val actions = ArrayList<Notification.Action>()
+ val action = constructAction("Test Action", immutablePendingIntent)
+ actions.add(action)
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsUriFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(actions)
+
+ val quickShareAction =
+ saveImageTask.createQuickShareAction(
+ constructAction("Test Action", immutablePendingIntent),
+ testScreenshotId,
+ testUri,
+ testImageTime,
+ testBitmap,
+ testUser,
+ )!!
+
+ assertEquals("Test Action", quickShareAction.title)
+ assertEquals(
+ immutablePendingIntent,
+ quickShareAction.actionIntent.intent.extras!!.getParcelable(
+ ScreenshotController.EXTRA_ACTION_INTENT,
+ PendingIntent::class.java
+ )
+ )
+ }
+
+ private fun constructAction(title: String, intent: PendingIntent): Notification.Action {
+ return Notification.Action.Builder(testIcon, title, intent).build()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index a5a9de54c558..8f2ee91d6a6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -1097,6 +1097,13 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
}
@Test
+ public void onShadeFlingEnd_mExpandImmediateShouldBeReset() {
+ mNotificationPanelViewController.onFlingEnd(false);
+
+ verify(mQsController).setExpandImmediate(false);
+ }
+
+ @Test
public void inUnlockedSplitShade_transitioningMaxTransitionDistance_makesShadeFullyExpanded() {
mStatusBarStateController.setState(SHADE);
enableSplitShade(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 20da8a619100..2da2e9238d0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -78,6 +78,7 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@@ -387,7 +388,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
whenever(clock.isLayoutRtl).thenReturn(false)
val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
- verify(clock).addOnLayoutChangeListener(capture(captor))
+ verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
verify(clock).pivotX = 0f
@@ -400,7 +401,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
whenever(clock.isLayoutRtl).thenReturn(true)
val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
- verify(clock).addOnLayoutChangeListener(capture(captor))
+ verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
verify(clock).pivotX = width.toFloat()
@@ -793,7 +794,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
@Test
fun clockPivotYInCenter() {
val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
- verify(clock).addOnLayoutChangeListener(capture(captor))
+ verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
var height = 100
val width = 50
@@ -825,16 +826,17 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
}
@Test
- fun carrierLeftPaddingIsSetWhenClockLayoutChanges() {
- val width = 200
- whenever(clock.width).thenReturn(width)
- whenever(clock.scaleX).thenReturn(2.57f) // 2.57 comes from qs_header.xml
- val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+ fun carrierStartPaddingIsSetOnClockLayout() {
+ val clockWidth = 200
+ val maxClockScale = context.resources.getFloat(R.dimen.qqs_expand_clock_scale)
+ val expectedStartPadding = (clockWidth * maxClockScale).toInt()
+ whenever(clock.width).thenReturn(clockWidth)
- verify(clock).addOnLayoutChangeListener(capture(captor))
- captor.value.onLayoutChange(clock, 0, 0, width, 0, 0, 0, 0, 0)
+ val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+ verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
+ captor.allValues.forEach { clock.executeLayoutChange(0, 0, clockWidth, 0, it) }
- verify(carrierGroup).setPaddingRelative(514, 0, 0, 0)
+ verify(carrierGroup).setPaddingRelative(expectedStartPadding, 0, 0, 0)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 39ed5535ff3b..914301f2e830 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -60,9 +60,13 @@ import org.mockito.MockitoAnnotations
class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
@Mock private lateinit var systemEventCoordinator: SystemEventCoordinator
+
@Mock private lateinit var statusBarWindowController: StatusBarWindowController
+
@Mock private lateinit var statusBarContentInsetProvider: StatusBarContentInsetsProvider
+
@Mock private lateinit var dumpManager: DumpManager
+
@Mock private lateinit var listener: SystemStatusAnimationCallback
private lateinit var systemClock: FakeSystemClock
@@ -380,6 +384,32 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
}
@Test
+ fun testPrivacyDot_isRemovedDuringChipDisappearAnimation() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+
+ // create and schedule high priority event
+ createAndScheduleFakePrivacyEvent()
+
+ // fast forward to ANIMATING_OUT state
+ fastForwardAnimationToState(ANIMATING_OUT)
+ assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+ verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
+
+ // remove persistent dot
+ systemStatusAnimationScheduler.removePersistentDot()
+ testScheduler.runCurrent()
+
+ // skip disappear animation
+ animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+ testScheduler.runCurrent()
+
+ // verify that animationState changes to IDLE and onHidePersistentDot callback is invoked
+ assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
+ verify(listener, times(1)).onHidePersistentDot()
+ }
+
+ @Test
fun testPrivacyEvent_forceVisibleIsUpdated_whenRescheduledDuringQueuedState() = runTest {
// Instantiate class under test with TestScope from runTest
initializeSystemStatusAnimationScheduler(testScope = this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 6a0e3c6d51eb..02666e40b98d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.when;
import android.content.res.Resources;
import android.metrics.LogMaker;
import android.testing.AndroidTestingRunner;
+import android.view.View;
import androidx.test.filters.SmallTest;
@@ -430,6 +431,84 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
verify(mNotificationStackScrollLayout).setStatusBarState(KEYGUARD);
}
+ @Test
+ public void updateImportantForAccessibility_noChild_onKeyGuard_notImportantForA11y() {
+ // GIVEN: Controller is attached, active notifications is empty,
+ // and mNotificationStackScrollLayout.onKeyguard() is true
+ initController(/* viewIsAttached= */ true);
+ when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true);
+ mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
+
+ // WHEN: call updateImportantForAccessibility
+ mController.updateImportantForAccessibility();
+
+ // THEN: mNotificationStackScrollLayout should not be important for A11y
+ verify(mNotificationStackScrollLayout)
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+
+ @Test
+ public void updateImportantForAccessibility_hasChild_onKeyGuard_importantForA11y() {
+ // GIVEN: Controller is attached, active notifications is not empty,
+ // and mNotificationStackScrollLayout.onKeyguard() is true
+ initController(/* viewIsAttached= */ true);
+ when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true);
+ mController.getNotifStackController().setNotifStats(
+ new NotifStats(
+ /* numActiveNotifs = */ 1,
+ /* hasNonClearableAlertingNotifs = */ false,
+ /* hasClearableAlertingNotifs = */ false,
+ /* hasNonClearableSilentNotifs = */ false,
+ /* hasClearableSilentNotifs = */ false)
+ );
+
+ // WHEN: call updateImportantForAccessibility
+ mController.updateImportantForAccessibility();
+
+ // THEN: mNotificationStackScrollLayout should be important for A11y
+ verify(mNotificationStackScrollLayout)
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
+ @Test
+ public void updateImportantForAccessibility_hasChild_notOnKeyGuard_importantForA11y() {
+ // GIVEN: Controller is attached, active notifications is not empty,
+ // and mNotificationStackScrollLayout.onKeyguard() is false
+ initController(/* viewIsAttached= */ true);
+ when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false);
+ mController.getNotifStackController().setNotifStats(
+ new NotifStats(
+ /* numActiveNotifs = */ 1,
+ /* hasNonClearableAlertingNotifs = */ false,
+ /* hasClearableAlertingNotifs = */ false,
+ /* hasNonClearableSilentNotifs = */ false,
+ /* hasClearableSilentNotifs = */ false)
+ );
+
+ // WHEN: call updateImportantForAccessibility
+ mController.updateImportantForAccessibility();
+
+ // THEN: mNotificationStackScrollLayout should be important for A11y
+ verify(mNotificationStackScrollLayout)
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
+ @Test
+ public void updateImportantForAccessibility_noChild_notOnKeyGuard_importantForA11y() {
+ // GIVEN: Controller is attached, active notifications is empty,
+ // and mNotificationStackScrollLayout.onKeyguard() is false
+ initController(/* viewIsAttached= */ true);
+ when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false);
+ mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
+
+ // WHEN: call updateImportantForAccessibility
+ mController.updateImportantForAccessibility();
+
+ // THEN: mNotificationStackScrollLayout should be important for A11y
+ verify(mNotificationStackScrollLayout)
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
private LogMaker logMatcher(int category, int type) {
return argThat(new LogMatcher(category, type));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index a9cfe7f27202..85b1ec108e98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -836,7 +836,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
- public void onShadeClosesWithAnimationWillResetSwipeState() {
+ public void onShadeClosesWithAnimationWillResetTouchState() {
// GIVEN shade is expanded
mStackScroller.setIsExpanded(true);
clearInvocations(mNotificationSwipeHelper);
@@ -846,12 +846,12 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.setIsExpanded(false);
mStackScroller.onExpansionStopped();
- // VERIFY swipe is reset
- verify(mNotificationSwipeHelper).resetSwipeState();
+ // VERIFY touch is reset
+ verify(mNotificationSwipeHelper).resetTouchState();
}
@Test
- public void onShadeClosesWithoutAnimationWillResetSwipeState() {
+ public void onShadeClosesWithoutAnimationWillResetTouchState() {
// GIVEN shade is expanded
mStackScroller.setIsExpanded(true);
clearInvocations(mNotificationSwipeHelper);
@@ -859,8 +859,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
// WHEN closing the shade without the animation
mStackScroller.setIsExpanded(false);
- // VERIFY swipe is reset
- verify(mNotificationSwipeHelper).resetSwipeState();
+ // VERIFY touch is reset
+ verify(mNotificationSwipeHelper).resetTouchState();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
new file mode 100644
index 000000000000..2013bb0a547e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.util.kotlin
+
+import android.content.ComponentName
+import android.content.pm.ComponentInfo
+import android.content.pm.PackageManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+internal class PackageManagerExtComponentEnabledTest(private val testCase: TestCase) :
+ SysuiTestCase() {
+
+ @Mock private lateinit var packageManager: PackageManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testComponentActuallyEnabled() {
+ whenever(packageManager.getComponentEnabledSetting(TEST_COMPONENT))
+ .thenReturn(testCase.componentEnabledSetting)
+ val componentInfo =
+ mock<ComponentInfo>() {
+ whenever(isEnabled).thenReturn(testCase.componentIsEnabled)
+ whenever(componentName).thenReturn(TEST_COMPONENT)
+ }
+
+ assertThat(packageManager.isComponentActuallyEnabled(componentInfo))
+ .isEqualTo(testCase.expected)
+ }
+
+ internal data class TestCase(
+ @PackageManager.EnabledState val componentEnabledSetting: Int,
+ val componentIsEnabled: Boolean,
+ val expected: Boolean,
+ ) {
+ override fun toString(): String {
+ return "WHEN(" +
+ "componentIsEnabled = $componentIsEnabled, " +
+ "componentEnabledSetting = ${enabledStateToString()}) then " +
+ "EXPECTED = $expected"
+ }
+
+ private fun enabledStateToString() =
+ when (componentEnabledSetting) {
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> "STATE_DEFAULT"
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> "STATE_DISABLED"
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED -> {
+ "STATE_DISABLED_UNTIL_USED"
+ }
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER -> "STATE_DISABLED_USER"
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> "STATE_ENABLED"
+ else -> "INVALID STATE"
+ }
+ }
+
+ companion object {
+ @Parameters(name = "{0}") @JvmStatic fun data(): Collection<TestCase> = testData
+
+ private val testDataComponentIsEnabled =
+ listOf(
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ componentIsEnabled = true,
+ expected = true,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER,
+ componentIsEnabled = true,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ componentIsEnabled = true,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting =
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+ componentIsEnabled = true,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+ componentIsEnabled = true,
+ expected = true,
+ ),
+ )
+
+ private val testDataComponentIsDisabled =
+ listOf(
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ componentIsEnabled = false,
+ expected = true,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER,
+ componentIsEnabled = false,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ componentIsEnabled = false,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting =
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+ componentIsEnabled = false,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+ componentIsEnabled = false,
+ expected = false,
+ ),
+ )
+
+ private val testData = testDataComponentIsDisabled + testDataComponentIsEnabled
+
+ private val TEST_COMPONENT = ComponentName("pkg", "cls")
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt
new file mode 100644
index 000000000000..ff6b7d083df7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.qs.pipeline.data.repository
+
+import android.content.ComponentName
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeInstalledTilesComponentRepository : InstalledTilesComponentRepository {
+
+ private val installedComponentsPerUser =
+ mutableMapOf<Int, MutableStateFlow<Set<ComponentName>>>()
+
+ override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
+ return getFlow(userId).asStateFlow()
+ }
+
+ fun setInstalledPackagesForUser(userId: Int, components: Set<ComponentName>) {
+ getFlow(userId).value = components
+ }
+
+ private fun getFlow(userId: Int): MutableStateFlow<Set<ComponentName>> =
+ installedComponentsPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) }
+}
diff --git a/services/autofill/java/com/android/server/autofill/LogFieldClassificationScoreOnResultListener.java b/services/autofill/java/com/android/server/autofill/LogFieldClassificationScoreOnResultListener.java
new file mode 100644
index 000000000000..b4aca1530204
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/LogFieldClassificationScoreOnResultListener.java
@@ -0,0 +1,79 @@
+/*
+ * 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.server.autofill;
+
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.service.autofill.FieldClassification;
+import android.service.autofill.FillEventHistory.Event.NoSaveReason;
+import android.util.Slog;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager.AutofillCommitReason;
+
+import java.util.ArrayList;
+
+class LogFieldClassificationScoreOnResultListener implements
+ RemoteCallback.OnResultListener {
+
+ private static final String TAG = "LogFieldClassificationScoreOnResultListener";
+
+ private Session mSession;
+ private final @NoSaveReason int mSaveDialogNotShowReason;
+ private final @AutofillCommitReason int mCommitReason;
+ private final int mViewsSize;
+ private final AutofillId[] mAutofillIds;
+ private final String[] mUserValues;
+ private final String[] mCategoryIds;
+ private final ArrayList<AutofillId> mDetectedFieldIds;
+ private final ArrayList<FieldClassification> mDetectedFieldClassifications;
+ LogFieldClassificationScoreOnResultListener(Session session,
+ int saveDialogNotShowReason,
+ int commitReason, int viewsSize, AutofillId[] autofillIds, String[] userValues,
+ String[] categoryIds, ArrayList<AutofillId> detectedFieldIds,
+ ArrayList<FieldClassification> detectedFieldClassifications) {
+ this.mSession = session;
+ this.mSaveDialogNotShowReason = saveDialogNotShowReason;
+ this.mCommitReason = commitReason;
+ this.mViewsSize = viewsSize;
+ this.mAutofillIds = autofillIds;
+ this.mUserValues = userValues;
+ this.mCategoryIds = categoryIds;
+ this.mDetectedFieldIds = detectedFieldIds;
+ this.mDetectedFieldClassifications = detectedFieldClassifications;
+ }
+
+ public void onResult(@Nullable Bundle result) {
+ // Create a local copy to safe guard race condition
+ Session session = mSession;
+ if (session == null) {
+ Slog.wtf(TAG, "session is null when calling onResult()");
+ return;
+ }
+ session.handleLogFieldClassificationScore(
+ result,
+ mSaveDialogNotShowReason,
+ mCommitReason,
+ mViewsSize,
+ mAutofillIds,
+ mUserValues,
+ mCategoryIds,
+ mDetectedFieldIds,
+ mDetectedFieldClassifications);
+ mSession = null;
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 563e431b5737..0c3f8667f4f8 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -3063,76 +3063,91 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
// Then use the results, asynchronously
- final RemoteCallback callback = new RemoteCallback((result) -> {
- if (result == null) {
- if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results");
- logContextCommitted(null, null, saveDialogNotShowReason, commitReason);
- return;
- }
- final Scores scores = result.getParcelable(EXTRA_SCORES, android.service.autofill.AutofillFieldClassificationService.Scores.class);
- if (scores == null) {
- Slog.w(TAG, "No field classification score on " + result);
- return;
- }
- int i = 0, j = 0;
- try {
- // Iteract over all autofill fields first
- for (i = 0; i < viewsSize; i++) {
- final AutofillId autofillId = autofillIds[i];
-
- // Search the best scores for each category (as some categories could have
- // multiple user values
- ArrayMap<String, Float> scoresByField = null;
- for (j = 0; j < userValues.length; j++) {
- final String categoryId = categoryIds[j];
- final float score = scores.scores[i][j];
- if (score > 0) {
- if (scoresByField == null) {
- scoresByField = new ArrayMap<>(userValues.length);
- }
- final Float currentScore = scoresByField.get(categoryId);
- if (currentScore != null && currentScore > score) {
- if (sVerbose) {
- Slog.v(TAG, "skipping score " + score
- + " because it's less than " + currentScore);
- }
- continue;
- }
+ final RemoteCallback callback = new RemoteCallback(
+ new LogFieldClassificationScoreOnResultListener(
+ this,
+ saveDialogNotShowReason,
+ commitReason,
+ viewsSize,
+ autofillIds,
+ userValues,
+ categoryIds,
+ detectedFieldIds,
+ detectedFieldClassifications));
+
+ fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds,
+ defaultAlgorithm, defaultArgs, algorithms, args);
+ }
+
+ void handleLogFieldClassificationScore(@Nullable Bundle result, int saveDialogNotShowReason,
+ int commitReason, int viewsSize, AutofillId[] autofillIds, String[] userValues,
+ String[] categoryIds, ArrayList<AutofillId> detectedFieldIds,
+ ArrayList<FieldClassification> detectedFieldClassifications) {
+ if (result == null) {
+ if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results");
+ logContextCommitted(null, null, saveDialogNotShowReason, commitReason);
+ return;
+ }
+ final Scores scores = result.getParcelable(EXTRA_SCORES,
+ android.service.autofill.AutofillFieldClassificationService.Scores.class);
+ if (scores == null) {
+ Slog.w(TAG, "No field classification score on " + result);
+ return;
+ }
+ int i = 0, j = 0;
+ try {
+ // Iteract over all autofill fields first
+ for (i = 0; i < viewsSize; i++) {
+ final AutofillId autofillId = autofillIds[i];
+
+ // Search the best scores for each category (as some categories could have
+ // multiple user values
+ ArrayMap<String, Float> scoresByField = null;
+ for (j = 0; j < userValues.length; j++) {
+ final String categoryId = categoryIds[j];
+ final float score = scores.scores[i][j];
+ if (score > 0) {
+ if (scoresByField == null) {
+ scoresByField = new ArrayMap<>(userValues.length);
+ }
+ final Float currentScore = scoresByField.get(categoryId);
+ if (currentScore != null && currentScore > score) {
if (sVerbose) {
- Slog.v(TAG, "adding score " + score + " at index " + j + " and id "
- + autofillId);
+ Slog.v(TAG, "skipping score " + score
+ + " because it's less than " + currentScore);
}
- scoresByField.put(categoryId, score);
- } else if (sVerbose) {
- Slog.v(TAG, "skipping score 0 at index " + j + " and id " + autofillId);
+ continue;
}
+ if (sVerbose) {
+ Slog.v(TAG, "adding score " + score + " at index " + j + " and id "
+ + autofillId);
+ }
+ scoresByField.put(categoryId, score);
+ } else if (sVerbose) {
+ Slog.v(TAG, "skipping score 0 at index " + j + " and id " + autofillId);
}
- if (scoresByField == null) {
- if (sVerbose) Slog.v(TAG, "no score for autofillId=" + autofillId);
- continue;
- }
-
- // Then create the matches for that autofill id
- final ArrayList<Match> matches = new ArrayList<>(scoresByField.size());
- for (j = 0; j < scoresByField.size(); j++) {
- final String fieldId = scoresByField.keyAt(j);
- final float score = scoresByField.valueAt(j);
- matches.add(new Match(fieldId, score));
- }
- detectedFieldIds.add(autofillId);
- detectedFieldClassifications.add(new FieldClassification(matches));
- } // for i
- } catch (ArrayIndexOutOfBoundsException e) {
- wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e);
- return;
- }
-
- logContextCommitted(detectedFieldIds, detectedFieldClassifications,
- saveDialogNotShowReason, commitReason);
- });
+ }
+ if (scoresByField == null) {
+ if (sVerbose) Slog.v(TAG, "no score for autofillId=" + autofillId);
+ continue;
+ }
- fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds,
- defaultAlgorithm, defaultArgs, algorithms, args);
+ // Then create the matches for that autofill id
+ final ArrayList<Match> matches = new ArrayList<>(scoresByField.size());
+ for (j = 0; j < scoresByField.size(); j++) {
+ final String fieldId = scoresByField.keyAt(j);
+ final float score = scoresByField.valueAt(j);
+ matches.add(new Match(fieldId, score));
+ }
+ detectedFieldIds.add(autofillId);
+ detectedFieldClassifications.add(new FieldClassification(matches));
+ } // for i
+ } catch (ArrayIndexOutOfBoundsException e) {
+ wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e);
+ return;
+ }
+ logContextCommitted(detectedFieldIds, detectedFieldClassifications,
+ saveDialogNotShowReason, commitReason);
}
/**
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 6db8ea7e536e..6f205636b476 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -70,6 +70,7 @@ import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_FGS_PERMI
import static android.os.PowerExemptionManager.REASON_OPT_OUT_REQUESTED;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_PLATFORM_VPN;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_VPN;
+import static android.os.PowerExemptionManager.REASON_OTHER;
import static android.os.PowerExemptionManager.REASON_PACKAGE_INSTALLER;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
@@ -85,7 +86,6 @@ import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE;
import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
import static android.os.PowerExemptionManager.REASON_TEMP_ALLOWED_WHILE_IN_USE;
import static android.os.PowerExemptionManager.REASON_UID_VISIBLE;
-import static android.os.PowerExemptionManager.REASON_UNKNOWN;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.PowerExemptionManager.getReasonCodeFromProcState;
import static android.os.PowerExemptionManager.reasonCodeToString;
@@ -2156,9 +2156,7 @@ public final class ActiveServices {
}
}
- final boolean fgsTypeChangingFromShortFgs = r.isForeground && isOldTypeShortFgs;
-
- if (fgsTypeChangingFromShortFgs) {
+ if (r.isForeground && isOldTypeShortFgs) {
// If we get here, that means startForeground(SHORT_SERVICE) is called again
// on a SHORT_SERVICE FGS.
@@ -2211,9 +2209,7 @@ public final class ActiveServices {
// "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
}
}
- }
-
- if (!fgsTypeChangingFromShortFgs && r.mStartForegroundCount == 0) {
+ } else if (r.mStartForegroundCount == 0) {
/*
If the service was started with startService(), not
startForegroundService(), and if startForeground() isn't called within
@@ -2244,7 +2240,7 @@ public final class ActiveServices {
r.mLoggedInfoAllowStartForeground = false;
}
}
- } else if (!fgsTypeChangingFromShortFgs && r.mStartForegroundCount >= 1) {
+ } else if (r.mStartForegroundCount >= 1) {
// We get here if startForeground() is called multiple times
// on the same service after it's created, regardless of whether
// stopForeground() has been called or not.
@@ -7428,33 +7424,30 @@ public final class ActiveServices {
boolean isStartService) {
// Check DeviceConfig flag.
if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
+ if (!r.mAllowWhileInUsePermissionInFgs) {
+ // BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
+ // Note REASON_OTHER since there's no other suitable reason.
+ r.mAllowWhileInUsePermissionInFgsReason = REASON_OTHER;
+ }
r.mAllowWhileInUsePermissionInFgs = true;
}
- final @ReasonCode int allowWhileInUse;
-
- // Either (or both) mAllowWhileInUsePermissionInFgs or mAllowStartForeground is
- // newly allowed?
- boolean newlyAllowed = false;
if (!r.mAllowWhileInUsePermissionInFgs
|| (r.mAllowStartForeground == REASON_DENIED)) {
- allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
- callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges,
- isBindService);
+ @ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
+ callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges);
// We store them to compare the old and new while-in-use logics to each other.
// (They're not used for any other purposes.)
if (!r.mAllowWhileInUsePermissionInFgs) {
r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
+ r.mAllowWhileInUsePermissionInFgsReason = allowWhileInUse;
}
if (r.mAllowStartForeground == REASON_DENIED) {
r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked(
allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
backgroundStartPrivileges, isBindService);
}
- } else {
- allowWhileInUse = REASON_UNKNOWN;
}
- r.mAllowWhileInUsePermissionInFgsReason = allowWhileInUse;
}
/**
@@ -7476,7 +7469,7 @@ public final class ActiveServices {
}
final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, null /* targetProcess */,
- BackgroundStartPrivileges.NONE, false);
+ BackgroundStartPrivileges.NONE);
@ReasonCode int allowStartFgs = shouldAllowFgsStartForegroundNoBindingCheckLocked(
allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */,
BackgroundStartPrivileges.NONE);
@@ -7500,19 +7493,15 @@ public final class ActiveServices {
*/
private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
int callingPid, int callingUid, @Nullable ProcessRecord targetProcess,
- BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) {
+ BackgroundStartPrivileges backgroundStartPrivileges) {
int ret = REASON_DENIED;
- final boolean forStartForeground = !isBindService;
-
- if (forStartForeground) {
- final int uidState = mAm.getUidStateLocked(callingUid);
- if (ret == REASON_DENIED) {
- // Allow FGS while-in-use if the caller's process state is PROCESS_STATE_PERSISTENT,
- // PROCESS_STATE_PERSISTENT_UI or PROCESS_STATE_TOP.
- if (uidState <= PROCESS_STATE_TOP) {
- ret = getReasonCodeFromProcState(uidState);
- }
+ final int uidState = mAm.getUidStateLocked(callingUid);
+ if (ret == REASON_DENIED) {
+ // Allow FGS while-in-use if the caller's process state is PROCESS_STATE_PERSISTENT,
+ // PROCESS_STATE_PERSISTENT_UI or PROCESS_STATE_TOP.
+ if (uidState <= PROCESS_STATE_TOP) {
+ ret = getReasonCodeFromProcState(uidState);
}
}
@@ -7733,7 +7722,7 @@ public final class ActiveServices {
shouldAllowFgsWhileInUsePermissionLocked(
clientPackageName,
clientPid, clientUid, null /* targetProcess */,
- BackgroundStartPrivileges.NONE, false);
+ BackgroundStartPrivileges.NONE);
final @ReasonCode int allowStartFgs =
shouldAllowFgsStartForegroundNoBindingCheckLocked(
allowWhileInUse2,
@@ -8162,7 +8151,7 @@ public final class ActiveServices {
String callingPackage) {
return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
/* targetProcess */ null,
- BackgroundStartPrivileges.NONE, false)
+ BackgroundStartPrivileges.NONE)
!= REASON_DENIED;
}
@@ -8170,7 +8159,7 @@ public final class ActiveServices {
String callingPackage, @Nullable ProcessRecord targetProcess,
@NonNull BackgroundStartPrivileges backgroundStartPrivileges) {
return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
- targetProcess, backgroundStartPrivileges, false) != REASON_DENIED;
+ targetProcess, backgroundStartPrivileges) != REASON_DENIED;
}
/**
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 0b5b1cb2902e..4b6d32427d68 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -25,6 +25,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
+import android.app.BroadcastOptions;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.os.SystemClock;
@@ -257,7 +258,10 @@ class BroadcastProcessQueue {
deferredStatesApplyConsumer.accept(record, recordIndex);
}
- if (record.isReplacePending()) {
+ // Ignore FLAG_RECEIVER_REPLACE_PENDING if the sender specified the policy using the
+ // BroadcastOptions delivery group APIs.
+ if (record.isReplacePending()
+ && record.getDeliveryGroupPolicy() == BroadcastOptions.DELIVERY_GROUP_POLICY_ALL) {
final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex);
if (replacedBroadcastRecord != null) {
return replacedBroadcastRecord;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index f6004d7d2b7f..8688f25ba002 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -269,7 +269,7 @@ public class BroadcastQueueImpl extends BroadcastQueue {
Activity.RESULT_CANCELED, null, null,
false, false, oldRecord.shareIdentity, oldRecord.userId,
oldRecord.callingUid, r.callingUid, r.callerPackage,
- SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0);
+ SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0, 0);
} catch (RemoteException e) {
Slog.w(TAG, "Failure ["
+ mQueueName + "] sending broadcast result of "
@@ -339,7 +339,7 @@ public class BroadcastQueueImpl extends BroadcastQueue {
private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
BroadcastRecord r, String typeForLogging) {
final Intent intent = r.intent;
- for (int i = queue.size() - 1; i > 0; i--) {
+ for (int i = queue.size() - 1; i >= 0; i--) {
final BroadcastRecord old = queue.get(i);
if (old.userId == r.userId && intent.filterEquals(old.intent)) {
if (DEBUG_BROADCAST) {
@@ -617,7 +617,10 @@ public class BroadcastQueueImpl extends BroadcastQueue {
r.curApp.info.packageName,
r.callerPackage,
r.calculateTypeForLogging(),
- r.getDeliveryGroupPolicy());
+ r.getDeliveryGroupPolicy(),
+ r.intent.getFlags(),
+ BroadcastRecord.getReceiverPriority(curReceiver),
+ r.callerProcState);
}
if (state == BroadcastRecord.IDLE) {
Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
@@ -748,7 +751,7 @@ public class BroadcastQueueImpl extends BroadcastQueue {
Intent intent, int resultCode, String data, Bundle extras,
boolean ordered, boolean sticky, boolean shareIdentity, int sendingUser,
int receiverUid, int callingUid, String callingPackage,
- long dispatchDelay, long receiveDelay) throws RemoteException {
+ long dispatchDelay, long receiveDelay, int priority) throws RemoteException {
// If the broadcaster opted-in to sharing their identity, then expose package visibility for
// the receiver.
if (shareIdentity) {
@@ -798,7 +801,8 @@ public class BroadcastQueueImpl extends BroadcastQueue {
dispatchDelay, receiveDelay, 0 /* finish_delay */,
SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
app != null ? app.info.packageName : null, callingPackage,
- r.calculateTypeForLogging(), r.getDeliveryGroupPolicy());
+ r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
+ priority, r.callerProcState);
}
}
@@ -879,7 +883,7 @@ public class BroadcastQueueImpl extends BroadcastQueue {
r.resultExtras, r.ordered, r.initialSticky, r.shareIdentity, r.userId,
filter.receiverList.uid, r.callingUid, r.callerPackage,
r.dispatchTime - r.enqueueTime,
- r.receiverTime - r.dispatchTime);
+ r.receiverTime - r.dispatchTime, filter.getPriority());
// parallel broadcasts are fire-and-forget, not bookended by a call to
// finishReceiverLocked(), so we manage their activity-start token here
if (filter.receiverList.app != null
@@ -1170,7 +1174,7 @@ public class BroadcastQueueImpl extends BroadcastQueue {
r.resultData, r.resultExtras, false, false, r.shareIdentity,
r.userId, r.callingUid, r.callingUid, r.callerPackage,
r.dispatchTime - r.enqueueTime,
- now - r.dispatchTime);
+ now - r.dispatchTime, 0);
logBootCompletedBroadcastCompletionLatencyIfPossible(r);
// Set this to null so that the reference
// (local and remote) isn't kept in the mBroadcastHistory.
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index cbc75401f0b8..8380308812d5 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -719,11 +719,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
private void skipAndCancelReplacedBroadcasts(ArraySet<BroadcastRecord> replacedBroadcasts) {
for (int i = 0; i < replacedBroadcasts.size(); ++i) {
final BroadcastRecord r = replacedBroadcasts.valueAt(i);
- r.resultCode = Activity.RESULT_CANCELED;
- r.resultData = null;
- r.resultExtras = null;
- scheduleResultTo(r);
- notifyFinishBroadcast(r);
+ // Skip all the receivers in the replaced broadcast
+ for (int rcvrIdx = 0; rcvrIdx < r.receivers.size(); ++rcvrIdx) {
+ if (!isDeliveryStateTerminal(r.getDeliveryState(rcvrIdx))) {
+ mBroadcastConsumerSkipAndCanceled.accept(r, rcvrIdx);
+ }
+ }
}
}
@@ -1932,7 +1933,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName,
receiverType, type, dispatchDelay, receiveDelay, finishDelay, packageState,
app != null ? app.info.packageName : null, r.callerPackage,
- r.calculateTypeForLogging(), r.getDeliveryGroupPolicy());
+ r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
+ BroadcastRecord.getReceiverPriority(receiver), r.callerProcState);
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index cfdb13393e80..67d43fd30bad 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -44,6 +44,8 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
+import android.app.ActivityManager;
+import android.app.ActivityManager.ProcessState;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.BackgroundStartPrivileges;
@@ -93,6 +95,7 @@ final class BroadcastRecord extends Binder {
final @Nullable String callerFeatureId; // which feature in the package sent this
final int callingPid; // the pid of who sent this
final int callingUid; // the uid of who sent this
+ final @ProcessState int callerProcState; // Procstate of the caller process at enqueue time.
final int originalStickyCallingUid;
// if this is a sticky broadcast, the Uid of the original sender
@@ -462,6 +465,8 @@ final class BroadcastRecord extends Binder {
callerFeatureId = _callerFeatureId;
callingPid = _callingPid;
callingUid = _callingUid;
+ callerProcState = callerApp == null ? ActivityManager.PROCESS_STATE_UNKNOWN
+ : callerApp.getCurProcState();
callerInstantApp = _callerInstantApp;
callerInstrumented = isCallerInstrumented(_callerApp, _callingUid);
resolvedType = _resolvedType;
@@ -515,6 +520,7 @@ final class BroadcastRecord extends Binder {
callerFeatureId = from.callerFeatureId;
callingPid = from.callingPid;
callingUid = from.callingUid;
+ callerProcState = from.callerProcState;
callerInstantApp = from.callerInstantApp;
callerInstrumented = from.callerInstrumented;
ordered = from.ordered;
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 80406e6f66e4..38e7371e7075 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -113,6 +113,11 @@ public class ForegroundServiceTypeLoggerModule {
// We use this to get the duration an API was active after
// the stop call.
final SparseArray<Long> mLastFgsTimeStamp = new SparseArray<>();
+
+ // A map of API types to first FGS start call timestamps
+ // We use this to get the duration an API was active after
+ // the stop call.
+ final SparseArray<Long> mFirstFgsTimeStamp = new SparseArray<>();
}
// SparseArray that tracks all UIDs that have made various
@@ -146,6 +151,7 @@ public class ForegroundServiceTypeLoggerModule {
if (fgsIndex < 0) {
uidState.mRunningFgs.put(apiType, new ArrayMap<>());
fgsIndex = uidState.mRunningFgs.indexOfKey(apiType);
+ uidState.mFirstFgsTimeStamp.put(apiType, System.currentTimeMillis());
}
final ArrayMap<ComponentName, ServiceRecord> fgsList =
uidState.mRunningFgs.valueAt(fgsIndex);
@@ -237,7 +243,7 @@ public class ForegroundServiceTypeLoggerModule {
// there's no more FGS running for this type, just get rid of it
uidState.mRunningFgs.remove(apiType);
// but we need to keep track of the timestamp in case an API stops
- uidState.mLastFgsTimeStamp.put(apiType, record.mFgsExitTime);
+ uidState.mLastFgsTimeStamp.put(apiType, System.currentTimeMillis());
}
}
if (!apisFound.isEmpty()) {
@@ -454,8 +460,17 @@ public class ForegroundServiceTypeLoggerModule {
public void logFgsApiEvent(ServiceRecord r, int fgsState,
@FgsApiState int apiState,
@ForegroundServiceApiType int apiType, long timestamp) {
- final long apiDurationBeforeFgsStart = r.mFgsEnterTime - timestamp;
- final long apiDurationAfterFgsEnd = timestamp - r.mFgsExitTime;
+ long apiDurationBeforeFgsStart = r.createRealTime - timestamp;
+ long apiDurationAfterFgsEnd = timestamp - r.mFgsExitTime;
+ UidState uidState = mUids.get(r.appInfo.uid);
+ if (uidState != null) {
+ if (uidState.mFirstFgsTimeStamp.contains(apiType)) {
+ apiDurationBeforeFgsStart = uidState.mFirstFgsTimeStamp.get(apiType) - timestamp;
+ }
+ if (uidState.mLastFgsTimeStamp.contains(apiType)) {
+ apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
+ }
+ }
final int[] apiTypes = new int[1];
apiTypes[0] = apiType;
final long[] timeStamps = new long[1];
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 7841b699ec98..ffe5a6e6b958 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -18,6 +18,7 @@ package com.android.server.am;
import android.annotation.UptimeMillisLong;
import android.app.ActivityManagerInternal.OomAdjReason;
+import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -320,5 +321,8 @@ final class ProcessCachedOptimizerRecord {
pw.print(prefix); pw.print("isFreezeExempt="); pw.print(mFreezeExempt);
pw.print(" isPendingFreeze="); pw.print(mPendingFreeze);
pw.print(" " + IS_FROZEN + "="); pw.println(mFrozen);
+ pw.print(prefix); pw.print("earliestFreezableTimeMs=");
+ TimeUtils.formatDuration(mEarliestFreezableTimeMillis, nowUptime, pw);
+ pw.println();
}
}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index d6495c78574c..267d2464e0d5 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -669,6 +669,11 @@ class ProcessRecord implements WindowProcessListener {
return mOnewayThread;
}
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getCurProcState() {
+ return mState.getCurProcState();
+ }
+
@GuardedBy({"mService", "mProcLock"})
public void makeActive(IApplicationThread thread, ProcessStatsService tracker) {
mProfile.onProcessActive(thread, tracker);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 8c227f5488d3..9db9e77c82c9 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -72,6 +72,10 @@ public class SettingsToPropertiesMapper {
Settings.Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED,
};
+ // TODO(b/282593625): Move this constant to DeviceConfig module
+ private static final String NAMESPACE_TETHERING_U_OR_LATER_NATIVE =
+ "tethering_u_or_later_native";
+
// All the flags under the listed DeviceConfig scopes will be synced to native level.
//
// NOTE: please grant write permission system property prefix
@@ -106,7 +110,8 @@ public class SettingsToPropertiesMapper {
DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE_BOOT,
DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE,
- DeviceConfig.NAMESPACE_HDMI_CONTROL
+ DeviceConfig.NAMESPACE_HDMI_CONTROL,
+ NAMESPACE_TETHERING_U_OR_LATER_NATIVE
};
private final String[] mGlobalSettings;
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index a2582083c409..a6677a5185ca 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -429,21 +429,23 @@ public class UidObserverController {
}
}
- pw.println();
- pw.print(" mUidChangeDispatchCount=");
- pw.print(mUidChangeDispatchCount);
- pw.println();
- pw.println(" Slow UID dispatches:");
- for (int i = 0; i < count; i++) {
- final UidObserverRegistration reg = (UidObserverRegistration)
- mUidObservers.getRegisteredCallbackCookie(i);
- pw.print(" ");
- pw.print(mUidObservers.getRegisteredCallbackItem(i).getClass().getTypeName());
- pw.print(": ");
- pw.print(reg.mSlowDispatchCount);
- pw.print(" / Max ");
- pw.print(reg.mMaxDispatchTime);
- pw.println("ms");
+ if (dumpPackage == null) {
+ pw.println();
+ pw.print(" mUidChangeDispatchCount=");
+ pw.print(mUidChangeDispatchCount);
+ pw.println();
+ pw.println(" Slow UID dispatches:");
+ for (int i = 0; i < count; i++) {
+ final UidObserverRegistration reg = (UidObserverRegistration)
+ mUidObservers.getRegisteredCallbackCookie(i);
+ pw.print(" ");
+ pw.print(mUidObservers.getRegisteredCallbackItem(i).getClass().getTypeName());
+ pw.print(": ");
+ pw.print(reg.mSlowDispatchCount);
+ pw.print(" / Max ");
+ pw.print(reg.mMaxDispatchTime);
+ pw.println("ms");
+ }
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d0b6cdce037f..336f0fb5699f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3277,6 +3277,7 @@ public class AudioService extends IAudioService.Stub
if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1
streamType = mVolumeControlStream;
} else {
+ // TODO discard activity on a muted stream?
final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);
final boolean activeForReal;
if (maybeActiveStreamType == AudioSystem.STREAM_RING
@@ -3480,9 +3481,10 @@ public class AudioService extends IAudioService.Stub
}
} else if (isStreamMutedByRingerOrZenMode(streamTypeAlias) && streamState.mIsMuted) {
// if the stream is currently muted streams by ringer/zen mode
- // then it cannot be unmuted (without FLAG_ALLOW_RINGER_MODES)
+ // then it cannot be unmuted (without FLAG_ALLOW_RINGER_MODES) with an unmute or raise
if (direction == AudioManager.ADJUST_TOGGLE_MUTE
- || direction == AudioManager.ADJUST_UNMUTE) {
+ || direction == AudioManager.ADJUST_UNMUTE
+ || direction == AudioManager.ADJUST_RAISE) {
adjustVolume = false;
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index aa5f9fa96875..7fa4d6ce4d49 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -573,7 +573,7 @@ public class BiometricScheduler {
final BiometricSchedulerOperation operation = mCurrentOperation;
mHandler.postDelayed(() -> {
if (operation == mCurrentOperation) {
- Counter.logIncrement("biometric.scheduler_watchdog_triggered_count");
+ Counter.logIncrement("biometric.value_scheduler_watchdog_triggered_count");
clearScheduler();
}
}, 10000);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e56eba659d37..b39e8606e189 100755..100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -119,6 +119,7 @@ import static android.service.notification.NotificationListenerService.TRIM_LIGH
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -223,6 +224,8 @@ import android.os.IInterface;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -234,6 +237,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.VibrationEffect;
+import android.os.WorkSource;
import android.permission.PermissionManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -559,6 +563,7 @@ public class NotificationManagerService extends SystemService {
private PermissionHelper mPermissionHelper;
private UsageStatsManagerInternal mUsageStatsManagerInternal;
private TelecomManager mTelecomManager;
+ private PowerManager mPowerManager;
private PostNotificationTrackerFactory mPostNotificationTrackerFactory;
final IBinder mForegroundToken = new Binder();
@@ -923,7 +928,7 @@ public class NotificationManagerService extends SystemService {
if (oldFlags != flags) {
summary.getSbn().getNotification().flags = flags;
mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
@@ -1457,7 +1462,7 @@ public class NotificationManagerService extends SystemService {
// want to adjust the flag behaviour.
mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(),
r, true /* isAppForeground*/,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
}
@@ -1488,7 +1493,7 @@ public class NotificationManagerService extends SystemService {
mHandler.post(
new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r,
/* foreground= */ true,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
}
@@ -1843,7 +1848,7 @@ public class NotificationManagerService extends SystemService {
}
} else if (action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (userHandle >= 0) {
+ if (userHandle >= 0 && !mDpm.isKeepProfilesRunningEnabled()) {
cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
REASON_PROFILE_TURNED_OFF, null);
mSnoozeHelper.clearData(userHandle);
@@ -2233,7 +2238,7 @@ public class NotificationManagerService extends SystemService {
UsageStatsManagerInternal usageStatsManagerInternal,
TelecomManager telecomManager, NotificationChannelLogger channelLogger,
SystemUiSystemPropertiesFlags.FlagResolver flagResolver,
- PermissionManager permissionManager,
+ PermissionManager permissionManager, PowerManager powerManager,
PostNotificationTrackerFactory postNotificationTrackerFactory) {
mHandler = handler;
Resources resources = getContext().getResources();
@@ -2265,6 +2270,7 @@ public class NotificationManagerService extends SystemService {
mDpm = dpm;
mUm = userManager;
mTelecomManager = telecomManager;
+ mPowerManager = powerManager;
mPostNotificationTrackerFactory = postNotificationTrackerFactory;
mPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
@@ -2568,6 +2574,7 @@ public class NotificationManagerService extends SystemService {
getContext().getSystemService(TelecomManager.class),
new NotificationChannelLoggerImpl(), SystemUiSystemPropertiesFlags.getResolver(),
getContext().getSystemService(PermissionManager.class),
+ getContext().getSystemService(PowerManager.class),
new PostNotificationTrackerFactory() {});
publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
@@ -2684,7 +2691,7 @@ public class NotificationManagerService extends SystemService {
final boolean isAppForeground =
mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
@@ -6577,7 +6584,7 @@ public class NotificationManagerService extends SystemService {
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int incomingUserId, boolean postSilently) {
- PostNotificationTracker tracker = mPostNotificationTrackerFactory.newTracker();
+ PostNotificationTracker tracker = acquireWakeLockForPost(pkg, callingUid);
boolean enqueued = false;
try {
enqueued = enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id,
@@ -6589,6 +6596,22 @@ public class NotificationManagerService extends SystemService {
}
}
+ private PostNotificationTracker acquireWakeLockForPost(String pkg, int uid) {
+ if (mFlagResolver.isEnabled(WAKE_LOCK_FOR_POSTING_NOTIFICATION)) {
+ // The package probably doesn't have WAKE_LOCK permission and should not require it.
+ return Binder.withCleanCallingIdentity(() -> {
+ WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "NotificationManagerService:post:" + pkg);
+ wakeLock.setWorkSource(new WorkSource(uid, pkg));
+ // TODO(b/275044361): Adjust to a more reasonable number when we have the data.
+ wakeLock.acquire(30_000);
+ return mPostNotificationTrackerFactory.newTracker(wakeLock);
+ });
+ } else {
+ return mPostNotificationTrackerFactory.newTracker(null);
+ }
+ }
+
/**
* @return True if we successfully processed the notification and handed off the task of
* enqueueing it to a background thread; false otherwise.
@@ -7106,7 +7129,7 @@ public class NotificationManagerService extends SystemService {
mHandler.post(
new NotificationManagerService.EnqueueNotificationRunnable(
r.getUser().getIdentifier(), r, isAppForeground,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
}
@@ -12168,20 +12191,20 @@ public class NotificationManagerService extends SystemService {
}
interface PostNotificationTrackerFactory {
- default PostNotificationTracker newTracker() {
- return new PostNotificationTracker();
+ default PostNotificationTracker newTracker(@Nullable WakeLock optionalWakelock) {
+ return new PostNotificationTracker(optionalWakelock);
}
}
static class PostNotificationTracker {
@ElapsedRealtimeLong private final long mStartTime;
- @Nullable private NotificationRecordLogger.NotificationReported mReport;
+ @Nullable private final WakeLock mWakeLock;
private boolean mOngoing;
@VisibleForTesting
- PostNotificationTracker() {
- // TODO(b/275044361): (Conditionally) receive a wakelock.
+ PostNotificationTracker(@Nullable WakeLock wakeLock) {
mStartTime = SystemClock.elapsedRealtime();
+ mWakeLock = wakeLock;
mOngoing = true;
if (DBG) {
Slog.d(TAG, "PostNotification: Started");
@@ -12199,9 +12222,8 @@ public class NotificationManagerService extends SystemService {
}
/**
- * Cancels the tracker (TODO(b/275044361): releasing the acquired WakeLock). Either
- * {@link #finish} or {@link #cancel} (exclusively) should be called on this object before
- * it's discarded.
+ * Cancels the tracker (releasing the acquired WakeLock). Either {@link #finish} or
+ * {@link #cancel} (exclusively) should be called on this object before it's discarded.
*/
void cancel() {
if (!isOngoing()) {
@@ -12209,9 +12231,9 @@ public class NotificationManagerService extends SystemService {
return;
}
mOngoing = false;
-
- // TODO(b/275044361): Release wakelock.
-
+ if (mWakeLock != null) {
+ Binder.withCleanCallingIdentity(() -> mWakeLock.release());
+ }
if (DBG) {
long elapsedTime = SystemClock.elapsedRealtime() - mStartTime;
Slog.d(TAG, TextUtils.formatSimple("PostNotification: Abandoned after %d ms",
@@ -12220,9 +12242,9 @@ public class NotificationManagerService extends SystemService {
}
/**
- * Finishes the tracker (TODO(b/275044361): releasing the acquired WakeLock) and returns the
- * time elapsed since the operation started, in milliseconds. Either {@link #finish} or
- * {@link #cancel} (exclusively) should be called on this object before it's discarded.
+ * Finishes the tracker (releasing the acquired WakeLock) and returns the time elapsed since
+ * the operation started, in milliseconds. Either {@link #finish} or {@link #cancel}
+ * (exclusively) should be called on this object before it's discarded.
*/
@DurationMillisLong
long finish() {
@@ -12232,9 +12254,9 @@ public class NotificationManagerService extends SystemService {
return elapsedTime;
}
mOngoing = false;
-
- // TODO(b/275044361): Release wakelock.
-
+ if (mWakeLock != null) {
+ Binder.withCleanCallingIdentity(() -> mWakeLock.release());
+ }
if (DBG) {
Slog.d(TAG,
TextUtils.formatSimple("PostNotification: Finished in %d ms", elapsedTime));
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 06db5be349d4..50f1673cae44 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2145,7 +2145,7 @@ final class InstallPackageHelper {
final String pkgName = pkg.getPackageName();
final int[] installedForUsers = installRequest.getOriginUsers();
final int installReason = installRequest.getInstallReason();
- final String installerPackageName = installRequest.getSourceInstallerPackageName();
+ final String installerPackageName = installRequest.getInstallerPackageName();
if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getPath());
synchronized (mPm.mLock) {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 95e790450724..34648740d54c 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -366,12 +366,6 @@ final class InstallRequest {
public String getApexModuleName() {
return mApexModuleName;
}
-
- @Nullable
- public String getSourceInstallerPackageName() {
- return mInstallArgs.mInstallSource.mInstallerPackageName;
- }
-
public boolean isRollback() {
return mInstallArgs != null
&& mInstallArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 6491fd1b1f98..a9115371413c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -511,7 +511,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
} catch (FileNotFoundException e) {
// Missing sessions are okay, probably first boot
- } catch (IOException | XmlPullParserException e) {
+ } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) {
Slog.wtf(TAG, "Failed reading install sessions", e);
} finally {
IoUtils.closeQuietly(fis);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index bba8043af5be..db47306ad58e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3409,7 +3409,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
@Nullable
- private String getDevicePolicyManagementRoleHolderPackageName(int userId) {
+ public String getDevicePolicyManagementRoleHolderPackageName(int userId) {
return Binder.withCleanCallingIdentity(() -> {
RoleManager roleManager = mContext.getSystemService(RoleManager.class);
List<String> roleHolders =
diff --git a/services/core/java/com/android/server/pm/ResilientAtomicFile.java b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
index 19aa4f8e8d0b..54ca426a6dc3 100644
--- a/services/core/java/com/android/server/pm/ResilientAtomicFile.java
+++ b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
@@ -230,7 +230,9 @@ final class ResilientAtomicFile implements Closeable {
+ Log.getStackTraceString(e));
}
- mCurrentFile.delete();
+ if (!mCurrentFile.delete()) {
+ throw new IllegalStateException("Failed to remove " + mCurrentFile);
+ }
mCurrentFile = null;
}
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index ba825774daf6..08934c69e099 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -724,6 +724,10 @@ public final class SuspendPackageHelper {
for (PackageInfo info : pkgInfos) {
result.add(info.packageName);
}
+
+ // Role holder may be null, but ArraySet handles it correctly.
+ result.remove(mPm.getDevicePolicyManagementRoleHolderPackageName(userId));
+
return result;
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d108e1487564..d55f85cde5af 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -986,11 +986,14 @@ public class PackageInfoUtils {
}
/** @see ApplicationInfo#privateFlagsExt */
- public static int appInfoPrivateFlagsExt(int pkgWithoutStateFlags,
+ private static int appInfoPrivateFlagsExt(int pkgWithoutStateFlags,
@Nullable PackageStateInternal pkgSetting) {
// @formatter:off
- // TODO: Add state specific flags
- return pkgWithoutStateFlags;
+ int flags = pkgWithoutStateFlags;
+ if (pkgSetting != null) {
+ flags |= flag(pkgSetting.getCpuAbiOverride() != null, ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE);
+ }
+ return flags;
// @formatter:on
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index b8c5b3f5524a..695a0cf3f79d 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -40,13 +40,17 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
-import android.app.AppOpsManager;
import android.app.SynchronousUserSwitchObserver;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -62,6 +66,7 @@ import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.BatterySaverPolicyConfig;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
@@ -277,6 +282,18 @@ public final class PowerManagerService extends SystemService
*/
private static final long ENHANCED_DISCHARGE_PREDICTION_BROADCAST_MIN_DELAY_MS = 60 * 1000L;
+ /**
+ * Apps targeting Android U and above need to define
+ * {@link android.Manifest.permission#TURN_SCREEN_ON} in their manifest for
+ * {@link android.os.PowerManager#ACQUIRE_CAUSES_WAKEUP} to have any effect.
+ * Note that most applications should use {@link android.R.attr#turnScreenOn} or
+ * {@link android.app.Activity#setTurnScreenOn(boolean)} instead, as this prevents the
+ * previous foreground app from being resumed first when the screen turns on.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long REQUIRE_TURN_SCREEN_ON_PERMISSION = 216114297L;
+
/** Reason ID for holding display suspend blocker. */
private static final String HOLDING_DISPLAY_SUSPEND_BLOCKER = "holding display";
@@ -304,8 +321,9 @@ public final class PowerManagerService extends SystemService
private final SystemPropertiesWrapper mSystemProperties;
private final Clock mClock;
private final Injector mInjector;
+ private final PermissionCheckerWrapper mPermissionCheckerWrapper;
+ private final PowerPropertiesWrapper mPowerPropertiesWrapper;
- private AppOpsManager mAppOpsManager;
private LightsManager mLightsManager;
private BatteryManagerInternal mBatteryManagerInternal;
private DisplayManagerInternal mDisplayManagerInternal;
@@ -1029,11 +1047,66 @@ public final class PowerManagerService extends SystemService
return new LowPowerStandbyController(context, looper);
}
- AppOpsManager createAppOpsManager(Context context) {
- return context.getSystemService(AppOpsManager.class);
+ PermissionCheckerWrapper createPermissionCheckerWrapper() {
+ return PermissionChecker::checkPermissionForDataDelivery;
+ }
+
+ PowerPropertiesWrapper createPowerPropertiesWrapper() {
+ return new PowerPropertiesWrapper() {
+ @Override
+ public boolean waive_target_sdk_check_for_turn_screen_on() {
+ return PowerProperties.waive_target_sdk_check_for_turn_screen_on().orElse(
+ false);
+ }
+
+ @Override
+ public boolean permissionless_turn_screen_on() {
+ return PowerProperties.permissionless_turn_screen_on().orElse(false);
+ }
+ };
}
}
+ /** Interface for checking an app op permission */
+ @VisibleForTesting
+ interface PermissionCheckerWrapper {
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has a given permission and whether the app op that corresponds to this permission
+ * is allowed.
+ * See {@link PermissionChecker#checkPermissionForDataDelivery} for more details.
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param pid The process id for which to check. Use {@link PermissionChecker#PID_UNKNOWN}
+ * if the PID is not known.
+ * @param attributionSource the permission identity
+ * @param message A message describing the reason the permission was checked
+ * @return The permission check result which is any of
+ * {@link PermissionChecker#PERMISSION_GRANTED},
+ * {@link PermissionChecker#PERMISSION_SOFT_DENIED},
+ * or {@link PermissionChecker#PERMISSION_HARD_DENIED}.
+ */
+ int checkPermissionForDataDelivery(@NonNull Context context, @NonNull String permission,
+ int pid, @NonNull AttributionSource attributionSource, @Nullable String message);
+ }
+
+ /** Interface for querying {@link PowerProperties} */
+ @VisibleForTesting
+ interface PowerPropertiesWrapper {
+ /**
+ * Waives the minimum target-sdk check for android.os.PowerManager#ACQUIRE_CAUSES_WAKEUP
+ * and only allows the flag for apps holding android.permission.TURN_SCREEN_ON
+ */
+ boolean waive_target_sdk_check_for_turn_screen_on();
+
+ /**
+ * Allows apps to turn the screen on with android.os.PowerManager#ACQUIRE_CAUSES_WAKEUP
+ * without being granted android.app.AppOpsManager#OP_TURN_SCREEN_ON.
+ */
+ boolean permissionless_turn_screen_on();
+ }
+
final Constants mConstants;
private native void nativeInit();
@@ -1086,8 +1159,8 @@ public final class PowerManagerService extends SystemService
Looper.getMainLooper());
mInattentiveSleepWarningOverlayController =
mInjector.createInattentiveSleepWarningController();
-
- mAppOpsManager = injector.createAppOpsManager(mContext);
+ mPermissionCheckerWrapper = mInjector.createPermissionCheckerWrapper();
+ mPowerPropertiesWrapper = mInjector.createPowerPropertiesWrapper();
mPowerGroupWakefulnessChangeListener = new PowerGroupWakefulnessChangeListener();
@@ -1562,19 +1635,29 @@ public final class PowerManagerService extends SystemService
}
@RequiresPermission(value = android.Manifest.permission.TURN_SCREEN_ON, conditional = true)
- private boolean isAcquireCausesWakeupFlagAllowed(String opPackageName, int opUid) {
+ private boolean isAcquireCausesWakeupFlagAllowed(String opPackageName, int opUid, int opPid) {
if (opPackageName == null) {
return false;
}
- if (mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON, opUid, opPackageName)
- == AppOpsManager.MODE_ALLOWED) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.TURN_SCREEN_ON)
- == PackageManager.PERMISSION_GRANTED) {
- Slog.i(TAG, "Allowing device wake-up from app " + opPackageName);
- return true;
- }
+ if (PermissionChecker.PERMISSION_GRANTED
+ == mPermissionCheckerWrapper.checkPermissionForDataDelivery(mContext,
+ android.Manifest.permission.TURN_SCREEN_ON, opPid,
+ new AttributionSource(opUid, opPackageName, /* attributionTag= */ null),
+ /* message= */ "ACQUIRE_CAUSES_WAKEUP for " + opPackageName)) {
+ Slog.i(TAG, "Allowing device wake-up from app " + opPackageName);
+ return true;
+ }
+ // CompatChanges#isChangeEnabled() returns false for apps with targetSdk < UDC and ensures
+ // backwards compatibility.
+ // waive_target_sdk_check_for_turn_screen_on() returns false by default and may be set to
+ // true on form factors with a more strict policy (e.g. TV)
+ if (!CompatChanges.isChangeEnabled(REQUIRE_TURN_SCREEN_ON_PERMISSION, opUid)
+ && !mPowerPropertiesWrapper.waive_target_sdk_check_for_turn_screen_on()) {
+ Slog.i(TAG, "Allowing device wake-up without android.permission.TURN_SCREEN_ON for "
+ + opPackageName);
+ return true;
}
- if (PowerProperties.permissionless_turn_screen_on().orElse(false)) {
+ if (mPowerPropertiesWrapper.permissionless_turn_screen_on()) {
Slog.d(TAG, "Device wake-up allowed by debug.power.permissionless_turn_screen_on");
return true;
}
@@ -1589,6 +1672,7 @@ public final class PowerManagerService extends SystemService
&& isScreenLock(wakeLock)) {
String opPackageName;
int opUid;
+ int opPid = PermissionChecker.PID_UNKNOWN;
if (wakeLock.mWorkSource != null && !wakeLock.mWorkSource.isEmpty()) {
WorkSource workSource = wakeLock.mWorkSource;
WorkChain workChain = getFirstNonEmptyWorkChain(workSource);
@@ -1603,10 +1687,12 @@ public final class PowerManagerService extends SystemService
} else {
opPackageName = wakeLock.mPackageName;
opUid = wakeLock.mOwnerUid;
+ opPid = wakeLock.mOwnerPid;
}
Integer powerGroupId = wakeLock.getPowerGroupId();
// powerGroupId is null if the wakelock associated display is no longer available
- if (powerGroupId != null && isAcquireCausesWakeupFlagAllowed(opPackageName, opUid)) {
+ if (powerGroupId != null
+ && isAcquireCausesWakeupFlagAllowed(opPackageName, opUid, opPid)) {
if (powerGroupId == Display.INVALID_DISPLAY_GROUP) {
// wake up all display groups
if (DEBUG_SPEW) {
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index bd07622ee5ca..10a2b9717555 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -167,6 +167,9 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer,
}
final TaskSnapshot recordSnapshotInner(TYPE source, boolean allowSnapshotHome) {
+ if (shouldDisableSnapshots()) {
+ return null;
+ }
final boolean snapshotHome = allowSnapshotHome && source.isActivityTypeHome();
final TaskSnapshot snapshot = captureSnapshot(source, snapshotHome);
if (snapshot == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2b2100e56f44..9f16a8441533 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4281,6 +4281,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
mTaskSupervisor.mStoppingActivities.remove(this);
+ mLetterboxUiController.destroy();
waitingToShow = false;
// Defer removal of this activity when either a child is animating, or app transition is on
@@ -4350,8 +4351,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
}
- mLetterboxUiController.destroy();
-
if (!delayed) {
updateReportedVisibilityLocked();
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 90af4c6236aa..1eb56f1b7d1c 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -280,6 +280,10 @@ class ActivityStartInterceptor {
return false;
}
+ if (isKeepProfilesRunningEnabled() && !isPackageSuspended()) {
+ return false;
+ }
+
IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT);
@@ -322,8 +326,7 @@ class ActivityStartInterceptor {
private boolean interceptSuspendedPackageIfNeeded() {
// Do not intercept if the package is not suspended
- if (mAInfo == null || mAInfo.applicationInfo == null ||
- (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) == 0) {
+ if (!isPackageSuspended()) {
return false;
}
final PackageManagerInternal pmi = mService.getPackageManagerInternalLocked();
@@ -467,6 +470,17 @@ class ActivityStartInterceptor {
return true;
}
+ private boolean isPackageSuspended() {
+ return mAInfo != null && mAInfo.applicationInfo != null
+ && (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) != 0;
+ }
+
+ private static boolean isKeepProfilesRunningEnabled() {
+ DevicePolicyManagerInternal dpmi =
+ LocalServices.getService(DevicePolicyManagerInternal.class);
+ return dpmi == null || dpmi.isKeepProfilesRunningEnabled();
+ }
+
/**
* Called when an activity is successfully launched.
*/
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a650d7709b67..d332dfdbadb4 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2012,7 +2012,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
synchronized (mGlobalLock) {
final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);
if (dc == null) return;
- final Task task = dc.getTask((t) -> t.isLeafTask() && t.isFocusable(),
+ final Task task = dc.getTask((t) -> t.isLeafTask() && t.isTopActivityFocusable(),
true /* traverseTopToBottom */);
if (task == null) return;
setFocusedTask(task.mTaskId, null /* touchedActivity */);
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index 123a74dbf597..f7ccc0d91969 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -23,6 +23,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Handler;
@@ -178,6 +179,14 @@ class AppWarnings {
* @param r activity record for which the warning may be displayed
*/
public void showDeprecatedAbiDialogIfNeeded(ActivityRecord r) {
+ final boolean isUsingAbiOverride = (r.info.applicationInfo.privateFlagsExt
+ & ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE) != 0;
+ if (isUsingAbiOverride) {
+ // The abiOverride flag was specified during installation, which means that if the app
+ // is currently running in 32-bit mode, it is intended. Do not show the warning dialog.
+ return;
+ }
+ // The warning dialog can also be disabled for debugging purpose
final boolean disableDeprecatedAbiDialog = SystemProperties.getBoolean(
"debug.wm.disable_deprecated_abi_dialog", false);
if (disableDeprecatedAbiDialog) {
diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java
index 31b1069dd022..db4762e5f877 100644
--- a/services/core/java/com/android/server/wm/DeviceStateController.java
+++ b/services/core/java/com/android/server/wm/DeviceStateController.java
@@ -111,9 +111,13 @@ final class DeviceStateController {
}
/**
- * @return true if the rotation direction on the Z axis should be reversed.
+ * @return true if the rotation direction on the Z axis should be reversed for the default
+ * display.
*/
- boolean shouldReverseRotationDirectionAroundZAxis() {
+ boolean shouldReverseRotationDirectionAroundZAxis(@NonNull DisplayContent displayContent) {
+ if (!displayContent.isDefaultDisplay) {
+ return false;
+ }
return ArrayUtils.contains(mReverseRotationAroundZAxisStates, mCurrentState);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6aec96988a77..7fc86b0fdc01 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5602,17 +5602,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
@WindowManager.TransitionFlags int flags) {
- prepareAppTransition(transit, flags);
- mTransitionController.requestTransitionIfNeeded(transit, flags,
- null /* trigger */, this);
+ requestTransitionAndLegacyPrepare(transit, flags, null /* trigger */);
}
/** @see #requestTransitionAndLegacyPrepare(int, int) */
void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
- @Nullable WindowContainer trigger) {
- prepareAppTransition(transit);
- mTransitionController.requestTransitionIfNeeded(transit, 0 /* flags */,
- trigger, this);
+ @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger) {
+ prepareAppTransition(transit, flags);
+ mTransitionController.requestTransitionIfNeeded(transit, flags, trigger, this);
}
void executeAppTransition() {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 8be36f07a040..b681c198538f 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -950,7 +950,7 @@ public class DisplayRotation {
}
void freezeRotation(int rotation) {
- if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
+ if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) {
rotation = RotationUtils.reverseRotationDirectionAroundZAxis(rotation);
}
@@ -1225,7 +1225,7 @@ public class DisplayRotation {
if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) {
sensorRotation = -1;
}
- if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
+ if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) {
sensorRotation = RotationUtils.reverseRotationDirectionAroundZAxis(sensorRotation);
}
mLastSensorRotation = sensorRotation;
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 1fbf59301191..2b34bb22729d 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -42,6 +42,7 @@ import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.RefreshCallbackItem;
import android.app.servertransaction.ResumeActivityItem;
import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.hardware.camera2.CameraManager;
import android.os.Handler;
@@ -423,7 +424,18 @@ final class DisplayRotationCompatPolicy {
// for the activity embedding case.
if (topActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
&& isTreatmentEnabledForActivity(topActivity, /* mustBeFullscreen */ false)) {
- showToast(R.string.display_rotation_camera_compat_toast_in_split_screen);
+ final PackageManager packageManager = mWmService.mContext.getPackageManager();
+ try {
+ showToast(
+ R.string.display_rotation_camera_compat_toast_in_multi_window,
+ (String) packageManager.getApplicationLabel(
+ packageManager.getApplicationInfo(packageName, /* flags */ 0)));
+ } catch (PackageManager.NameNotFoundException e) {
+ ProtoLog.e(WM_DEBUG_ORIENTATION,
+ "DisplayRotationCompatPolicy: Multi-window toast not shown as "
+ + "package '%s' cannot be found.",
+ packageName);
+ }
}
}
}
@@ -434,6 +446,15 @@ final class DisplayRotationCompatPolicy {
() -> Toast.makeText(mWmService.mContext, stringRes, Toast.LENGTH_LONG).show());
}
+ @VisibleForTesting
+ void showToast(@StringRes int stringRes, @NonNull String applicationLabel) {
+ UiThread.getHandler().post(
+ () -> Toast.makeText(
+ mWmService.mContext,
+ mWmService.mContext.getString(stringRes, applicationLabel),
+ Toast.LENGTH_LONG).show());
+ }
+
private synchronized void notifyCameraClosed(@NonNull String cameraId) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is notified that Camera %s is closed, scheduling rotation update.",
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 99878a3d0ffe..ad9c3b274267 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -19,17 +19,21 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
@@ -171,10 +175,11 @@ class KeyguardController {
final KeyguardDisplayState state = getDisplayState(displayId);
final boolean aodChanged = aodShowing != state.mAodShowing;
final boolean aodRemoved = state.mAodShowing && !aodShowing;
+ final boolean goingAwayRemoved = state.mKeyguardGoingAway && keyguardShowing;
// If keyguard is going away, but SystemUI aborted the transition, need to reset state.
// Do not reset keyguardChanged status when only AOD is removed.
final boolean keyguardChanged = (keyguardShowing != state.mKeyguardShowing)
- || (state.mKeyguardGoingAway && keyguardShowing && !aodRemoved);
+ || (goingAwayRemoved && !aodRemoved);
if (aodRemoved) {
updateDeferTransitionForAod(false /* waiting */);
}
@@ -214,6 +219,15 @@ class KeyguardController {
if (keyguardShowing) {
state.mDismissalRequested = false;
}
+ if (goingAwayRemoved) {
+ // Keyguard dismiss is canceled. Send a transition to undo the changes and clean up
+ // before holding the sleep token again.
+ final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
+ dc.requestTransitionAndLegacyPrepare(
+ TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
+ dc.mWallpaperController.showWallpaperInTransition(false /* showHome */);
+ mWindowManager.executeAppTransition();
+ }
}
// Update the sleep token first such that ensureActivitiesVisible has correct sleep token
@@ -269,7 +283,7 @@ class KeyguardController {
updateKeyguardSleepToken();
// Make the home wallpaper visible
- dc.mWallpaperController.showHomeWallpaperInTransition();
+ dc.mWallpaperController.showWallpaperInTransition(true /* showHome */);
// Some stack visibility might change (e.g. docked stack)
mRootWindowContainer.resumeFocusedTasksTopActivities();
mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
@@ -413,10 +427,12 @@ class KeyguardController {
if (isDisplayOccluded(DEFAULT_DISPLAY)) {
mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
TRANSIT_KEYGUARD_OCCLUDE,
+ TRANSIT_FLAG_KEYGUARD_OCCLUDING,
topActivity != null ? topActivity.getRootTask() : null);
} else {
mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
- TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */);
+ TRANSIT_KEYGUARD_UNOCCLUDE,
+ TRANSIT_FLAG_KEYGUARD_UNOCCLUDING);
}
updateKeyguardSleepToken(DEFAULT_DISPLAY);
mWindowManager.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b938b9e4ec51..9a5f766989ca 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -52,7 +52,6 @@ import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
-import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TASK_LAUNCHING_BEHIND;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -2820,9 +2819,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
}
}
- if (occludesKeyguard(wc)) {
- flags |= FLAG_OCCLUDES_KEYGUARD;
- }
if ((mFlags & FLAG_CHANGE_NO_ANIMATION) != 0
&& (mFlags & FLAG_CHANGE_YES_ANIMATION) == 0) {
flags |= FLAG_NO_ANIMATION;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 1c6a412e5450..0cb6f14b38f2 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -16,10 +16,10 @@
package com.android.server.wm;
+import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -619,9 +619,9 @@ class TransitionController {
}
// Make the collecting transition wait until this request is ready.
mCollectingTransition.setReady(readyGroupRef, false);
- if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
- // Add keyguard flag to dismiss keyguard
- mCollectingTransition.addFlag(flags);
+ if ((flags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
+ // Add keyguard flags to affect keyguard visibility
+ mCollectingTransition.addFlag(flags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS);
}
} else {
newTransition = requestStartTransition(createTransition(type, flags),
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 20ce98ca2fae..7f41ff1a3d9b 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -339,12 +339,12 @@ class WallpaperController {
}
/**
- * Change the visibility if wallpaper is home screen only.
+ * Make one wallpaper visible, according to {@attr showHome}.
* This is called during the keyguard unlocking transition
- * (see {@link KeyguardController#keyguardGoingAway(int, int)}) and thus assumes that if the
- * system wallpaper is shared with lock, then it needs no animation.
+ * (see {@link KeyguardController#keyguardGoingAway(int, int)}),
+ * or when a keyguard unlock is cancelled (see {@link KeyguardController})
*/
- public void showHomeWallpaperInTransition() {
+ public void showWallpaperInTransition(boolean showHome) {
updateWallpaperWindowsTarget(mFindResults);
if (!mFindResults.hasTopShowWhenLockedWallpaper()) {
@@ -357,9 +357,9 @@ class WallpaperController {
// Shared wallpaper, ensure its visibility
showWhenLocked.mToken.asWallpaperToken().updateWallpaperWindows(true);
} else {
- // Separate lock and home wallpapers: show home wallpaper and hide lock
- hideWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(true);
- showWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(false);
+ // Separate lock and home wallpapers: show the correct wallpaper in transition
+ hideWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(showHome);
+ showWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(!showHome);
}
}
@@ -401,6 +401,19 @@ class WallpaperController {
// swiping through Launcher pages).
final Rect wallpaperFrame = wallpaperWin.getFrame();
+ final int diffWidth = wallpaperFrame.width() - lastWallpaperBounds.width();
+ final int diffHeight = wallpaperFrame.height() - lastWallpaperBounds.height();
+ if ((wallpaperWin.mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0
+ && Math.abs(diffWidth) > 1 && Math.abs(diffHeight) > 1) {
+ Slog.d(TAG, "Skip wallpaper offset with inconsistent orientation, bounds="
+ + lastWallpaperBounds + " frame=" + wallpaperFrame);
+ // With FLAG_SCALED, the requested size should at least make the frame match one of
+ // side. If both sides contain differences, the client side may not have updated the
+ // latest size according to the current orientation. So skip calculating the offset to
+ // avoid the wallpaper not filling the screen.
+ return false;
+ }
+
int newXOffset = 0;
int newYOffset = 0;
boolean rawChanged = false;
@@ -417,7 +430,7 @@ class WallpaperController {
float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f;
// Difference between width of wallpaper image, and the last size of the wallpaper.
// This is the horizontal surplus from the prior configuration.
- int availw = wallpaperFrame.width() - lastWallpaperBounds.width();
+ int availw = diffWidth;
int displayOffset = getDisplayWidthOffset(availw, lastWallpaperBounds,
wallpaperWin.isRtl());
@@ -442,9 +455,7 @@ class WallpaperController {
float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f;
float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f;
- int availh = wallpaperWin.getFrame().bottom - wallpaperWin.getFrame().top
- - lastWallpaperBounds.height();
- offset = availh > 0 ? -(int)(availh * wpy + .5f) : 0;
+ offset = diffHeight > 0 ? -(int) (diffHeight * wpy + .5f) : 0;
if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
offset += mLastWallpaperDisplayOffsetY;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 5ba22830eec9..c918fb87154f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -16,6 +16,7 @@
package com.android.server.devicepolicy;
+import static android.app.admin.DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_TARGET_USER_ID;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_UPDATE_RESULT_KEY;
import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_CONFLICTING_ADMIN_POLICY;
@@ -176,6 +177,16 @@ final class DevicePolicyEngine {
}
boolean policyEnforced = Objects.equals(
localPolicyState.getCurrentResolvedPolicy(), value);
+ // TODO(b/285532044): remove hack and handle properly
+ if (!policyEnforced
+ && policyDefinition.getPolicyKey().getIdentifier().equals(
+ USER_CONTROL_DISABLED_PACKAGES_POLICY)) {
+ PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value;
+ PolicyValue<Set<String>> parsedResolvedValue =
+ (PolicyValue<Set<String>>) localPolicyState.getCurrentResolvedPolicy();
+ policyEnforced = (parsedResolvedValue != null && parsedValue != null
+ && parsedResolvedValue.getValue().containsAll(parsedValue.getValue()));
+ }
sendPolicyResultToAdmin(
enforcingAdmin,
policyDefinition,
@@ -418,6 +429,17 @@ final class DevicePolicyEngine {
boolean policyAppliedGlobally = Objects.equals(
globalPolicyState.getCurrentResolvedPolicy(), value);
+ // TODO(b/285532044): remove hack and handle properly
+ if (!policyAppliedGlobally
+ && policyDefinition.getPolicyKey().getIdentifier().equals(
+ USER_CONTROL_DISABLED_PACKAGES_POLICY)) {
+ PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value;
+ PolicyValue<Set<String>> parsedResolvedValue =
+ (PolicyValue<Set<String>>) globalPolicyState.getCurrentResolvedPolicy();
+ policyAppliedGlobally = (parsedResolvedValue != null && parsedValue != null
+ && parsedResolvedValue.getValue().containsAll(parsedValue.getValue()));
+ }
+
boolean policyApplied = policyAppliedGlobally && policyAppliedOnAllUsers;
sendPolicyResultToAdmin(
@@ -539,8 +561,20 @@ final class DevicePolicyEngine {
userId);
}
- isAdminPolicyApplied &= Objects.equals(
- value, localPolicyState.getCurrentResolvedPolicy());
+ // TODO(b/285532044): remove hack and handle properly
+ if (policyDefinition.getPolicyKey().getIdentifier().equals(
+ USER_CONTROL_DISABLED_PACKAGES_POLICY)) {
+ if (!Objects.equals(value, localPolicyState.getCurrentResolvedPolicy())) {
+ PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value;
+ PolicyValue<Set<String>> parsedResolvedValue =
+ (PolicyValue<Set<String>>) localPolicyState.getCurrentResolvedPolicy();
+ isAdminPolicyApplied &= (parsedResolvedValue != null && parsedValue != null
+ && parsedResolvedValue.getValue().containsAll(parsedValue.getValue()));
+ }
+ } else {
+ isAdminPolicyApplied &= Objects.equals(
+ value, localPolicyState.getCurrentResolvedPolicy());
+ }
}
return isAdminPolicyApplied;
}
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index 774f62d44045..fd478dc12c13 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -31,6 +31,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -245,10 +246,13 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks,
intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ ActivityOptions activityOptions = ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
IntentSender intentSender = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
- | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
- null, new UserHandle(mUserId)) .getIntentSender();
+ | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ activityOptions.toBundle(), new UserHandle(mUserId)).getIntentSender();
Bundle result = new Bundle();
result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob);
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index 1146271368af..3176f0673b65 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -106,6 +106,10 @@ android_test {
":PackageParserTestApp6",
],
resource_zips: [":PackageManagerServiceServerTests_apks_as_resources"],
+
+ data: [
+ ":StubTestApp",
+ ],
}
// Rules to copy all the test apks to the intermediate raw resource directory
diff --git a/services/tests/PackageManagerServiceTests/server/AndroidTest.xml b/services/tests/PackageManagerServiceTests/server/AndroidTest.xml
index 1b93527065ec..a0ef03c69e3a 100644
--- a/services/tests/PackageManagerServiceTests/server/AndroidTest.xml
+++ b/services/tests/PackageManagerServiceTests/server/AndroidTest.xml
@@ -23,6 +23,14 @@
<option name="install-arg" value="-g" />
<option name="test-file-name" value="PackageManagerServiceServerTests.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="settings put global verifier_engprod 1" />
+ </target_preparer>
+
+ <!-- Load additional APKs onto device -->
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push" value="StubTestApp.apk->/data/local/tmp/servicestests/StubTestApp.apk"/>
+ </target_preparer>
<option name="test-tag" value="PackageManagerServiceServerTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
index b82ffb4d0b39..435f0d7f5f15 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -32,7 +32,6 @@ import android.app.AppGlobals;
import android.content.IIntentReceiver;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
@@ -70,7 +69,7 @@ import java.util.regex.Pattern;
@RunWith(AndroidJUnit4.class)
public class PackageManagerServiceTest {
- private static final String PACKAGE_NAME = "com.android.frameworks.servicestests";
+ private static final String PACKAGE_NAME = "com.android.server.pm.test.service.server";
private static final String TEST_DATA_PATH = "/data/local/tmp/servicestests/";
private static final String TEST_APP_APK = "StubTestApp.apk";
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index dc92376263a6..ca4a4048cc00 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -66,6 +66,7 @@ import android.system.StructStat;
import android.test.AndroidTestCase;
import android.util.Log;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
@@ -2506,6 +2507,7 @@ public class PackageManagerTests extends AndroidTestCase {
}
@LargeTest
+ @FlakyTest(bugId = 283797480)
public void testCheckSignaturesRotatedAgainstRotated() throws Exception {
// checkSignatures should be successful when both apps have been signed with the same
// rotated key since the initial signature comparison between the two apps should
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 948687ae1645..582685cf009e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1407,7 +1407,7 @@ public final class BroadcastQueueModernImplTest {
eq(BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST),
eq(BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD),
anyLong(), anyLong(), anyLong(), anyInt(), nullable(String.class),
- anyString(), anyInt(), anyInt()),
+ anyString(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt()),
times(1));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 4989f841a275..03231ecfb723 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1904,6 +1904,34 @@ public class BroadcastQueueTest {
}
@Test
+ public void testReplacePending_diffReceivers() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp);
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp);
+ final BroadcastFilter receiverYellow = makeRegisteredReceiver(receiverYellowApp);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(receiverGreen, 10),
+ withPriority(receiverBlue, 5),
+ withPriority(receiverYellow, 0))));
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(receiverGreen, 10),
+ withPriority(receiverBlue, 5))));
+
+ waitForIdle();
+
+ verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
+ verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+ verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane);
+ }
+
+ @Test
public void testIdleAndBarrier() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
index cfef0b23b3e7..5fd270ecb2b4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
@@ -48,6 +48,7 @@ open class PackageHelperTestBase {
const val UNINSTALLER_PACKAGE = "com.android.test.known.uninstaller"
const val VERIFIER_PACKAGE = "com.android.test.known.verifier"
const val PERMISSION_CONTROLLER_PACKAGE = "com.android.test.known.permission"
+ const val MGMT_ROLE_HOLDER_PACKAGE = "com.android.test.know.device_management"
const val TEST_USER_ID = 0
}
@@ -119,6 +120,8 @@ open class PackageHelperTestBase {
Mockito.doReturn(arrayOf(PERMISSION_CONTROLLER_PACKAGE)).`when`(pms)
.getKnownPackageNamesInternal(any(),
eq(KnownPackages.PACKAGE_PERMISSION_CONTROLLER), eq(TEST_USER_ID))
+ Mockito.doReturn(MGMT_ROLE_HOLDER_PACKAGE).`when`(pms)
+ .getDevicePolicyManagementRoleHolderPackageName(eq(TEST_USER_ID))
}
private fun createPackageManagerService(vararg stageExistingPackages: String):
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index f9a8ead9cd4a..5cca5fa8ea0b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -128,13 +128,14 @@ class SuspendPackageHelperTest : PackageHelperTestBase() {
fun setPackagesSuspended_forQuietMode() {
val knownPackages = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE,
INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE,
- PERMISSION_CONTROLLER_PACKAGE)
+ PERMISSION_CONTROLLER_PACKAGE, MGMT_ROLE_HOLDER_PACKAGE)
val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
knownPackages, true /* suspended */, null /* appExtras */,
null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
TEST_USER_ID, deviceOwnerUid, true /* forQuietMode */)!!
- assertThat(failedNames.size).isEqualTo(0)
+ assertThat(failedNames.size).isEqualTo(1)
+ assertThat(failedNames[0]).isEqualTo(MGMT_ROLE_HOLDER_PACKAGE)
}
@Test
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index cfeaf0b54552..16e1b457ad96 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -114,7 +114,6 @@ android_test {
":SimpleServiceTestApp1",
":SimpleServiceTestApp2",
":SimpleServiceTestApp3",
- ":StubTestApp",
":SuspendTestApp",
":MediaButtonReceiverHolderTestHelperApp",
"data/broken_shortcut.xml",
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index b304968f3e69..fbb0ca108ecd 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -44,11 +44,6 @@
<option name="teardown-command" value="rm -rf /data/local/tmp/servicestests"/>
</target_preparer>
- <!-- Load additional APKs onto device -->
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
- <option name="push" value="StubTestApp.apk->/data/local/tmp/servicestests/StubTestApp.apk"/>
- </target_preparer>
-
<option name="test-tag" value="FrameworksServicesTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.frameworks.servicestests" />
diff --git a/services/tests/servicestests/src/com/android/server/credentials/MetricUtilitiesTest.java b/services/tests/servicestests/src/com/android/server/credentials/MetricUtilitiesTest.java
index b46f1a61c745..50ea48719948 100644
--- a/services/tests/servicestests/src/com/android/server/credentials/MetricUtilitiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/credentials/MetricUtilitiesTest.java
@@ -15,14 +15,27 @@
*/
package com.android.server.credentials;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.credentials.metrics.BrowsedAuthenticationMetric;
+import com.android.server.credentials.metrics.CandidateAggregateMetric;
+import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric;
+import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric;
import com.android.server.credentials.metrics.InitialPhaseMetric;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.security.cert.CertificateException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
/**
* Given the secondary-system nature of the MetricUtilities, we expect absolutely nothing to
* throw an error. If one presents itself, that is problematic.
@@ -33,6 +46,11 @@ import org.junit.runner.RunWith;
@SmallTest
public final class MetricUtilitiesTest {
+ @Before
+ public void setUp() throws CertificateException {
+ final Context context = ApplicationProvider.getApplicationContext();
+ }
+
@Test
public void logApiCalledInitialPhase_nullInitPhaseMetricAndNegativeSequence_success() {
MetricUtilities.logApiCalledInitialPhase(null, -1);
@@ -49,4 +67,102 @@ public final class MetricUtilitiesTest {
MetricUtilities.getHighlyUniqueInteger());
MetricUtilities.logApiCalledInitialPhase(validInitPhaseMetric, 1);
}
+
+ @Test
+ public void logApiCalledTotalCandidate_nullCandidateNegativeSequence_success() {
+ MetricUtilities.logApiCalledAggregateCandidate(null, -1);
+ }
+
+ @Test
+ public void logApiCalledTotalCandidate_invalidCandidatePhasePositiveSequence_success() {
+ MetricUtilities.logApiCalledAggregateCandidate(new CandidateAggregateMetric(-1), 1);
+ }
+
+ @Test
+ public void logApiCalledTotalCandidate_validPhaseMetric_success() {
+ MetricUtilities.logApiCalledAggregateCandidate(
+ new CandidateAggregateMetric(MetricUtilities.getHighlyUniqueInteger()), 1);
+ }
+
+ @Test
+ public void logApiCalledNoUidFinal_nullNoUidFinalNegativeSequenceAndStatus_success() {
+ MetricUtilities.logApiCalledNoUidFinal(null, null,
+ -1, -1);
+ }
+
+ @Test
+ public void logApiCalledNoUidFinal_invalidNoUidFinalPhasePositiveSequenceAndStatus_success() {
+ MetricUtilities.logApiCalledNoUidFinal(new ChosenProviderFinalPhaseMetric(-1, -1),
+ List.of(new CandidateBrowsingPhaseMetric()), 1, 1);
+ }
+
+ @Test
+ public void logApiCalledNoUidFinal_validNoUidFinalMetric_success() {
+ MetricUtilities.logApiCalledNoUidFinal(
+ new ChosenProviderFinalPhaseMetric(MetricUtilities.getHighlyUniqueInteger(),
+ MetricUtilities.getHighlyUniqueInteger()),
+ List.of(new CandidateBrowsingPhaseMetric()), 1, 1);
+ }
+
+ @Test
+ public void logApiCalledCandidate_nullMapNullInitFinalNegativeSequence_success() {
+ MetricUtilities.logApiCalledCandidatePhase(null, -1,
+ null);
+ }
+
+ @Test
+ public void logApiCalledCandidate_invalidProvidersCandidatePositiveSequence_success() {
+ Map<String, ProviderSession> testMap = new HashMap<>();
+ testMap.put("s", null);
+ MetricUtilities.logApiCalledCandidatePhase(testMap, 1,
+ null);
+ }
+
+ @Test
+ public void logApiCalledCandidateGet_nullMapFinalNegativeSequence_success() {
+ MetricUtilities.logApiCalledCandidateGetMetric(null, -1);
+ }
+
+ @Test
+ public void logApiCalledCandidateGet_invalidProvidersCandidatePositiveSequence_success() {
+ Map<String, ProviderSession> testMap = new HashMap<>();
+ testMap.put("s", null);
+ MetricUtilities.logApiCalledCandidateGetMetric(testMap, 1);
+ }
+
+ @Test
+ public void logApiCalledAuthMetric_nullAuthMetricNegativeSequence_success() {
+ MetricUtilities.logApiCalledAuthenticationMetric(null, -1);
+ }
+
+ @Test
+ public void logApiCalledAuthMetric_invalidAuthMetricPositiveSequence_success() {
+ MetricUtilities.logApiCalledAuthenticationMetric(new BrowsedAuthenticationMetric(-1), 1);
+ }
+
+ @Test
+ public void logApiCalledAuthMetric_nullAuthMetricPositiveSequence_success() {
+ MetricUtilities.logApiCalledAuthenticationMetric(
+ new BrowsedAuthenticationMetric(MetricUtilities.getHighlyUniqueInteger()), -1);
+ }
+
+ @Test
+ public void logApiCalledFinal_nullFinalNegativeSequenceAndStatus_success() {
+ MetricUtilities.logApiCalledFinalPhase(null, null,
+ -1, -1);
+ }
+
+ @Test
+ public void logApiCalledFinal_invalidFinalPhasePositiveSequenceAndStatus_success() {
+ MetricUtilities.logApiCalledFinalPhase(new ChosenProviderFinalPhaseMetric(-1, -1),
+ List.of(new CandidateBrowsingPhaseMetric()), 1, 1);
+ }
+
+ @Test
+ public void logApiCalledFinal_validFinalMetric_success() {
+ MetricUtilities.logApiCalledFinalPhase(
+ new ChosenProviderFinalPhaseMetric(MetricUtilities.getHighlyUniqueInteger(),
+ MetricUtilities.getHighlyUniqueInteger()),
+ List.of(new CandidateBrowsingPhaseMetric()), 1, 1);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 933f00231313..5c6164efb3b6 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -18,8 +18,6 @@ package com.android.server.power;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_ERRORED;
import static android.os.PowerManager.USER_ACTIVITY_EVENT_BUTTON;
import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
@@ -44,6 +42,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -51,13 +50,14 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManagerInternal;
-import android.app.AppOpsManager;
import android.attention.AttentionManagerInternal;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.AttributionSource;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.PackageManager;
+import android.content.PermissionChecker;
import android.content.res.Resources;
import android.hardware.SensorManager;
import android.hardware.display.AmbientDisplayConfiguration;
@@ -95,7 +95,6 @@ import com.android.server.lights.LightsManager;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.power.PowerManagerService.BatteryReceiver;
import com.android.server.power.PowerManagerService.BinderService;
-import com.android.server.power.PowerManagerService.Injector;
import com.android.server.power.PowerManagerService.NativeWrapper;
import com.android.server.power.PowerManagerService.UserSwitchedReceiver;
import com.android.server.power.PowerManagerService.WakeLock;
@@ -105,9 +104,14 @@ import com.android.server.power.batterysaver.BatterySaverStateMachine;
import com.android.server.power.batterysaver.BatterySavingStats;
import com.android.server.testutils.OffsettableClock;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
@@ -150,12 +154,13 @@ public class PowerManagerServiceTest {
@Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
@Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
@Mock private SystemPropertiesWrapper mSystemPropertiesMock;
- @Mock private AppOpsManager mAppOpsManagerMock;
@Mock private LowPowerStandbyController mLowPowerStandbyControllerMock;
@Mock private Callable<Void> mInvalidateInteractiveCachesMock;
+ @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
+ @Mock private PowerManagerService.PermissionCheckerWrapper mPermissionCheckerWrapperMock;
+ @Mock private PowerManagerService.PowerPropertiesWrapper mPowerPropertiesWrapper;
- @Mock
- private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
+ @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule();
private PowerManagerService mService;
private ContextWrapper mContextSpy;
@@ -231,7 +236,7 @@ public class PowerManagerServiceTest {
}
private PowerManagerService createService() {
- mService = new PowerManagerService(mContextSpy, new Injector() {
+ mService = new PowerManagerService(mContextSpy, new PowerManagerService.Injector() {
@Override
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
@@ -327,8 +332,13 @@ public class PowerManagerServiceTest {
}
@Override
- AppOpsManager createAppOpsManager(Context context) {
- return mAppOpsManagerMock;
+ PowerManagerService.PermissionCheckerWrapper createPermissionCheckerWrapper() {
+ return mPermissionCheckerWrapperMock;
+ }
+
+ @Override
+ PowerManagerService.PowerPropertiesWrapper createPowerPropertiesWrapper() {
+ return mPowerPropertiesWrapper;
}
});
return mService;
@@ -589,6 +599,7 @@ public class PowerManagerServiceTest {
}
@Test
+ @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
public void testWakefulnessAwake_AcquireCausesWakeup_turnScreenOnAllowed() {
createService();
startSystem();
@@ -597,11 +608,12 @@ public class PowerManagerServiceTest {
IBinder token = new Binder();
String tag = "acq_causes_wakeup";
String packageName = "pkg.name";
- when(mAppOpsManagerMock.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON,
- Binder.getCallingUid(), packageName)).thenReturn(MODE_ALLOWED);
- when(mContextSpy.checkCallingOrSelfPermission(
- android.Manifest.permission.TURN_SCREEN_ON)).thenReturn(
- PackageManager.PERMISSION_GRANTED);
+ AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+ packageName, /* attributionTag= */ null);
+
+ doReturn(PermissionChecker.PERMISSION_GRANTED).when(
+ mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+ eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
// First, ensure that a normal full wake lock does not cause a wakeup
int flags = PowerManager.FULL_WAKE_LOCK;
@@ -626,6 +638,35 @@ public class PowerManagerServiceTest {
}
@Test
+ @DisableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
+ public void testWakefulnessAwake_AcquireCausesWakeupOldSdk_turnScreenOnAllowed() {
+ createService();
+ startSystem();
+ forceSleep();
+
+ IBinder token = new Binder();
+ String tag = "acq_causes_wakeup";
+ String packageName = "pkg.name";
+ AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+ packageName, /* attributionTag= */ null);
+
+ // verify that the wakeup is allowed for apps targeting older sdks, and therefore won't have
+ // the TURN_SCREEN_ON permission granted
+ doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+ mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+ eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
+
+ doReturn(false).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
+
+ int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+ }
+
+ @Test
+ @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
public void testWakefulnessAwake_AcquireCausesWakeup_turnScreenOnDenied() {
createService();
startSystem();
@@ -634,30 +675,43 @@ public class PowerManagerServiceTest {
IBinder token = new Binder();
String tag = "acq_causes_wakeup";
String packageName = "pkg.name";
- when(mAppOpsManagerMock.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON,
- Binder.getCallingUid(), packageName)).thenReturn(MODE_ERRORED);
+ AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+ packageName, /* attributionTag= */ null);
+ doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+ mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+ eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
+ doReturn(false).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
+ doReturn(false).when(mPowerPropertiesWrapper).permissionless_turn_screen_on();
- // Verify that flag has no effect when OP_TURN_SCREEN_ON is not allowed
+ // Verify that flag has no effect when TURN_SCREEN_ON is not allowed for apps targeting U+
int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
- if (PowerProperties.permissionless_turn_screen_on().orElse(false)) {
- assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
- } else {
- assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
- }
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+ }
+
+ @Test
+ @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
+ public void testWakefulnessAwake_AcquireCausesWakeupOldSdk_turnScreenOnDenied() {
+ createService();
+ startSystem();
+ forceSleep();
+
+ IBinder token = new Binder();
+ String tag = "acq_causes_wakeup";
+ String packageName = "pkg.name";
+ AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+ packageName, /* attributionTag= */ null);
+ doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+ mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+ eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
- when(mAppOpsManagerMock.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON,
- Binder.getCallingUid(), packageName)).thenReturn(MODE_ALLOWED);
- when(mContextSpy.checkCallingOrSelfPermission(
- android.Manifest.permission.TURN_SCREEN_ON)).thenReturn(
- PackageManager.PERMISSION_DENIED);
+ doReturn(true).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
- // Verify that the flag has no effect when OP_TURN_SCREEN_ON is allowed but
- // android.permission.TURN_SCREEN_ON is denied
- flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+ // Verify that flag has no effect when TURN_SCREEN_ON is not allowed for apps targeting U+
+ int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
if (PowerProperties.permissionless_turn_screen_on().orElse(false)) {
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index f44c1d18614d..4315254f68a9 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
@@ -36,6 +37,7 @@
<uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" />
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
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 9166b3d75f60..2a0c745a0ffc 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -61,6 +61,7 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
+import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
@@ -80,6 +81,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
@@ -119,12 +121,14 @@ import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import android.Manifest;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -181,11 +185,14 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.WorkSource;
import android.permission.PermissionManager;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
@@ -351,6 +358,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private PermissionManager mPermissionManager;
@Mock
private DevicePolicyManagerInternal mDevicePolicyManager;
+ @Mock
+ private PowerManager mPowerManager;
+ private final ArrayList<WakeLock> mAcquiredWakeLocks = new ArrayList<>();
private final TestPostNotificationTrackerFactory mPostNotificationTrackerFactory =
new TestPostNotificationTrackerFactory();
@@ -431,8 +441,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private final List<PostNotificationTracker> mCreatedTrackers = new ArrayList<>();
@Override
- public PostNotificationTracker newTracker() {
- PostNotificationTracker tracker = PostNotificationTrackerFactory.super.newTracker();
+ public PostNotificationTracker newTracker(@Nullable WakeLock optionalWakeLock) {
+ PostNotificationTracker tracker = PostNotificationTrackerFactory.super.newTracker(
+ optionalWakeLock);
mCreatedTrackers.add(tracker);
return tracker;
}
@@ -563,6 +574,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
+ // Use the real PowerManager to back up the mock w.r.t. creating WakeLocks.
+ // This is because 1) we need a mock to verify() calls and tracking the created WakeLocks,
+ // but 2) PowerManager and WakeLock perform their own checks (e.g. correct arguments, don't
+ // call release twice, etc) and we want the test to fail if such misuse happens, too.
+ PowerManager realPowerManager = mContext.getSystemService(PowerManager.class);
+ when(mPowerManager.newWakeLock(anyInt(), anyString())).then(
+ (Answer<WakeLock>) invocation -> {
+ WakeLock wl = realPowerManager.newWakeLock(invocation.getArgument(0),
+ invocation.getArgument(1));
+ mAcquiredWakeLocks.add(wl);
+ return wl;
+ });
+ mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, true);
+
// apps allowed as convos
mService.setStringArrayResourceValue(PKG_O);
@@ -579,7 +604,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mock(TelephonyManager.class),
mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
mTelecomManager, mLogger, mTestFlagResolver, mPermissionManager,
- mPostNotificationTrackerFactory);
+ mPowerManager, mPostNotificationTrackerFactory);
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -686,6 +711,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@After
+ public void assertAllWakeLocksReleased() {
+ for (WakeLock wakeLock : mAcquiredWakeLocks) {
+ assertThat(wakeLock.isHeld()).isFalse();
+ }
+ }
+
+ @After
public void tearDown() throws Exception {
if (mFile != null) mFile.delete();
clearDeviceConfig();
@@ -1486,7 +1518,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -1507,7 +1539,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -1803,6 +1835,112 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void enqueueNotification_acquiresAndReleasesWakeLock() throws Exception {
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_acquiresAndReleasesWakeLock", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+ waitForIdle();
+
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_throws_acquiresAndReleasesWakeLock() throws Exception {
+ // Simulate not enqueued due to rejected inputs.
+ assertThrows(Exception.class,
+ () -> mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_throws_acquiresAndReleasesWakeLock", 0,
+ /* notification= */ null, 0));
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock() throws Exception {
+ // Simulate not enqueued due to snoozing inputs.
+ when(mSnoozeHelper.getSnoozeContextForUnpostedNotification(anyInt(), any(), any()))
+ .thenReturn("zzzzzzz");
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+ waitForIdle();
+
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_notPosted_acquiresAndReleasesWakeLock() throws Exception {
+ // Simulate enqueued but not posted due to missing small icon.
+ Notification notif = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .build();
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_notPosted_acquiresAndReleasesWakeLock", 0,
+ notif, 0);
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+ waitForIdle();
+
+ // NLSes were not called.
+ verify(mListeners, never()).prepareNotifyPostedLocked(any(), any(), anyBoolean());
+
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_setsWakeLockWorkSource() throws Exception {
+ // Use a "full" mock for the PowerManager (instead of the one that delegates to the real
+ // service) so we can return a mocked WakeLock that we can verify() on.
+ reset(mPowerManager);
+ WakeLock wakeLock = mock(WakeLock.class);
+ when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(wakeLock);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_setsWakeLockWorkSource", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+ waitForIdle();
+
+ InOrder inOrder = inOrder(mPowerManager, wakeLock);
+ inOrder.verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ inOrder.verify(wakeLock).setWorkSource(eq(new WorkSource(mUid, PKG)));
+ inOrder.verify(wakeLock).acquire(anyLong());
+ inOrder.verify(wakeLock).release();
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void enqueueNotification_wakeLockFlagOff_noWakeLock() throws Exception {
+ mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, false);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_setsWakeLockWorkSource", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+ waitForIdle();
+
+ verifyZeroInteractions(mPowerManager);
+ }
+
+ @Test
public void testCancelNonexistentNotification() throws Exception {
mBinderService.cancelNotificationWithTag(PKG, PKG,
"testCancelNonexistentNotification", 0, 0);
@@ -4361,7 +4499,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4380,7 +4518,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(), update.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4400,7 +4538,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(), update.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4420,7 +4558,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(),
- update.getUid(), mPostNotificationTrackerFactory.newTracker());
+ update.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4434,13 +4572,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
r = generateNotificationRecord(mTestNotificationChannel, 1, null, false);
r.setCriticality(CriticalNotificationExtractor.CRITICAL);
runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
mService.addEnqueuedNotification(r);
runnable.run();
@@ -5090,7 +5228,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.new PostNotificationRunnable(original.getKey(),
original.getSbn().getPackageName(),
original.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -5114,7 +5252,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(),
update.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -7536,7 +7674,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10234,7 +10372,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10251,7 +10389,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10268,7 +10406,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10361,7 +10499,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// normal blocked notifications - blocked
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10379,7 +10517,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10392,7 +10530,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats, never()).registerBlocked(any());
@@ -10406,7 +10544,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats, never()).registerBlocked(any());
@@ -10420,7 +10558,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10435,7 +10573,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10448,7 +10586,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 66c1e35754c5..81c573d8fb1e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -48,6 +48,7 @@ import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.os.Looper;
+import android.os.PowerManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.PermissionManager;
@@ -169,6 +170,7 @@ public class RoleObserverTest extends UiServiceTestCase {
mock(UsageStatsManagerInternal.class), mock(TelecomManager.class),
mock(NotificationChannelLogger.class), new TestableFlagResolver(),
mock(PermissionManager.class),
+ mock(PowerManager.class),
new NotificationManagerService.PostNotificationTrackerFactory() {});
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index cb984f814f1a..3ff433e546d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1996,7 +1996,8 @@ public class ActivityRecordTests extends WindowTestsBase {
assertTrue(activity.isSnapshotCompatible(snapshot));
- setRotatedScreenOrientationSilently(activity);
+ doReturn(task.getWindowConfiguration().getRotation() + 1).when(mDisplayContent)
+ .rotationForActivityInDifferentOrientation(activity);
assertFalse(activity.isSnapshotCompatible(snapshot));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 4890f3e6cbf1..bcb0c6b5c269 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -250,9 +250,22 @@ public class ActivityStartInterceptorTest {
}
@Test
- public void testInterceptQuietProfile() {
- // GIVEN that the user the activity is starting as is currently in quiet mode
+ public void testInterceptQuietProfile_keepProfilesRunningEnabled() {
+ // GIVEN that the user the activity is starting as is currently in quiet mode and
+ // profiles are kept running when in quiet mode.
when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
+ when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(true);
+
+ // THEN calling intercept returns false because package also has to be suspended.
+ assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+ }
+
+ @Test
+ public void testInterceptQuietProfile_keepProfilesRunningDisabled() {
+ // GIVEN that the user the activity is starting as is currently in quiet mode and
+ // profiles are stopped when in quiet mode (pre-U behavior, no profile app suspension).
+ when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
+ when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(false);
// THEN calling intercept returns true
assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
@@ -263,10 +276,28 @@ public class ActivityStartInterceptorTest {
}
@Test
- public void testInterceptQuietProfileWhenPackageSuspended() {
+ public void testInterceptQuietProfileWhenPackageSuspended_keepProfilesRunningEnabled() {
+ // GIVEN that the user the activity is starting as is currently in quiet mode,
+ // the package is suspended and profiles are kept running while in quiet mode.
+ suspendPackage("com.test.suspending.package");
+ when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
+ when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(true);
+
+ // THEN calling intercept returns true
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+
+ // THEN the returned intent is the quiet mode intent
+ assertTrue(UnlaunchableAppActivity.createInQuietModeDialogIntent(TEST_USER_ID)
+ .filterEquals(mInterceptor.mIntent));
+ }
+
+ @Test
+ public void testInterceptQuietProfileWhenPackageSuspended_keepProfilesRunningDisabled() {
+ // GIVEN that the user the activity is starting as is currently in quiet mode,
+ // the package is suspended and profiles are stopped while in quiet mode.
suspendPackage("com.test.suspending.package");
- // GIVEN that the user the activity is starting as is currently in quiet mode
when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
+ when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(false);
// THEN calling intercept returns true
assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 0044e2e54b5a..4290f4b9ee80 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -1059,4 +1060,18 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
assertEquals(0, mAtm.getActivityInterceptorCallbacks().size());
mAtm.mInternal.unregisterActivityStartInterceptor(SYSTEM_FIRST_ORDERED_ID);
}
+
+ @Test
+ public void testFocusTopTask() {
+ final ActivityRecord homeActivity = new ActivityBuilder(mAtm)
+ .setTask(mRootWindowContainer.getDefaultTaskDisplayArea().getOrCreateRootHomeTask())
+ .build();
+ final Task pinnedTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_PINNED)
+ .build();
+ mAtm.focusTopTask(mDisplayContent.mDisplayId);
+
+ assertTrue(homeActivity.getTask().isFocused());
+ assertFalse(pinnedTask.isFocused());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 3ca35ef7cc26..8e015d4d228d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -40,7 +40,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -51,6 +53,8 @@ import android.app.servertransaction.RefreshCallbackItem;
import android.app.servertransaction.ResumeActivityItem;
import android.content.ComponentName;
import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Configuration.Orientation;
import android.hardware.camera2.CameraManager;
@@ -84,7 +88,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
private static final String TEST_PACKAGE_2 = "com.test.package.two";
private static final String CAMERA_ID_1 = "camera-1";
private static final String CAMERA_ID_2 = "camera-2";
-
+ private static final String TEST_PACKAGE_1_LABEL = "testPackage1";
private CameraManager mMockCameraManager;
private Handler mMockHandler;
private LetterboxConfiguration mLetterboxConfiguration;
@@ -133,17 +137,27 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
}
@Test
- public void testOpenedCameraInSplitScreen_showToast() {
+ public void testOpenedCameraInSplitScreen_showToast() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
spyOn(mTask);
spyOn(mDisplayRotationCompatPolicy);
doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mTask).getWindowingMode();
+ final PackageManager mockPackageManager = mock(PackageManager.class);
+ final ApplicationInfo mockApplicationInfo = mock(ApplicationInfo.class);
+ when(mContext.getPackageManager()).thenReturn(mockPackageManager);
+ when(mockPackageManager.getApplicationInfo(anyString(), anyInt()))
+ .thenReturn(mockApplicationInfo);
+
+ doReturn(TEST_PACKAGE_1_LABEL).when(mockPackageManager)
+ .getApplicationLabel(mockApplicationInfo);
+
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
verify(mDisplayRotationCompatPolicy).showToast(
- R.string.display_rotation_camera_compat_toast_in_split_screen);
+ R.string.display_rotation_camera_compat_toast_in_multi_window,
+ TEST_PACKAGE_1_LABEL);
}
@Test
@@ -157,7 +171,8 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
verify(mDisplayRotationCompatPolicy, never()).showToast(
- R.string.display_rotation_camera_compat_toast_in_split_screen);
+ R.string.display_rotation_camera_compat_toast_in_multi_window,
+ TEST_PACKAGE_1_LABEL);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 04e1d9c07a07..2a8f0ffc4d49 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -59,10 +59,10 @@ import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.devicestate.DeviceStateManager;
+import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManagerInternal;
import android.os.SystemClock;
-import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.view.DisplayAddress;
@@ -518,7 +518,8 @@ public class DisplayRotationTests {
mBuilder.build();
configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
- when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true);
+ when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+ .thenReturn(true);
thawRotation();
@@ -544,7 +545,8 @@ public class DisplayRotationTests {
@Test
public void testFreezeRotation_reverseRotationDirectionAroundZAxis_yes() throws Exception {
mBuilder.build();
- when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true);
+ when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+ .thenReturn(true);
freezeRotation(Surface.ROTATION_90);
assertEquals(Surface.ROTATION_270, mTarget.getUserRotation());
@@ -553,7 +555,8 @@ public class DisplayRotationTests {
@Test
public void testFreezeRotation_reverseRotationDirectionAroundZAxis_no() throws Exception {
mBuilder.build();
- when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(false);
+ when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+ .thenReturn(false);
freezeRotation(Surface.ROTATION_90);
assertEquals(Surface.ROTATION_90, mTarget.getUserRotation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 2dd34eb5ac4d..082122e33175 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -406,7 +406,6 @@ public class SizeCompatTests extends WindowTestsBase {
clearInvocations(translucentActivity.mLetterboxUiController);
// We destroy the first opaque activity
- mActivity.setState(DESTROYED, "testing");
mActivity.removeImmediately();
// Check that updateInheritedLetterbox() is invoked again
@@ -4654,14 +4653,6 @@ public class SizeCompatTests extends WindowTestsBase {
return c;
}
- private static void resizeDisplay(DisplayContent displayContent, int width, int height) {
- displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity,
- displayContent.mBaseDisplayPhysicalXDpi, displayContent.mBaseDisplayPhysicalYDpi);
- final Configuration c = new Configuration();
- displayContent.computeScreenConfiguration(c);
- displayContent.onRequestedOverrideConfigurationChanged(c);
- }
-
private static void setNeverConstrainDisplayApisFlag(@Nullable String value,
boolean makeDefault) {
DeviceConfig.setProperty(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 01ddcca99300..f3d8114b9e94 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -127,6 +127,10 @@ public class WallpaperControllerTests extends WindowTestsBase {
public void testWallpaperSizeWithFixedTransform() {
// No wallpaper
final DisplayContent dc = mDisplayContent;
+ if (dc.mBaseDisplayHeight == dc.mBaseDisplayWidth) {
+ // Make sure the size is different when changing orientation.
+ resizeDisplay(dc, 500, 1000);
+ }
// No wallpaper WSA Surface
final WindowState wallpaperWindow = createWallpaperWindow(dc);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ddc729f773b2..be8ee7832a5d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -71,6 +71,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
@@ -946,6 +947,14 @@ class WindowTestsBase extends SystemServiceTestsBase {
dc.setRotationAnimation(null);
}
+ static void resizeDisplay(DisplayContent displayContent, int width, int height) {
+ displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity,
+ displayContent.mBaseDisplayPhysicalXDpi, displayContent.mBaseDisplayPhysicalYDpi);
+ final Configuration c = new Configuration();
+ displayContent.computeScreenConfiguration(c);
+ displayContent.onRequestedOverrideConfigurationChanged(c);
+ }
+
// The window definition for UseTestDisplay#addWindows. The test can declare to add only
// necessary windows, that avoids adding unnecessary overhead of unused windows.
static final int W_NOTIFICATION_SHADE = TYPE_NOTIFICATION_SHADE;
diff --git a/tests/InputMethodStressTest/AndroidTest.xml b/tests/InputMethodStressTest/AndroidTest.xml
index bedf0990a188..bfebb42ad244 100644
--- a/tests/InputMethodStressTest/AndroidTest.xml
+++ b/tests/InputMethodStressTest/AndroidTest.xml
@@ -19,6 +19,7 @@
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_abi_dialog 1" />
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
<option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
</target_preparer>