summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/multiuser/AndroidManifest.xml4
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java22
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobStore.java273
-rw-r--r--core/api/current.txt5
-rw-r--r--core/java/android/app/OWNERS2
-rw-r--r--core/java/android/app/servertransaction/ClientTransaction.java2
-rw-r--r--core/java/android/os/BinderProxy.java4
-rw-r--r--core/java/android/os/IUserManager.aidl2
-rw-r--r--core/java/android/os/UserManager.java11
-rw-r--r--core/java/android/provider/Settings.java16
-rw-r--r--core/java/android/service/controls/ControlsProviderService.java14
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java66
-rw-r--r--core/java/android/window/BackNavigationInfo.java36
-rw-r--r--core/java/android/window/TransitionInfo.java9
-rw-r--r--core/jni/AndroidRuntime.cpp30
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java4
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java38
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java463
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java291
-rw-r--r--data/etc/services.core.protolog.json12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java8
-rw-r--r--media/java/android/media/MediaRouter2.java2
-rw-r--r--media/java/android/media/MediaRouter2Manager.java10
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java18
-rw-r--r--packages/CredentialManager/res/values/strings.xml8
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt51
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt34
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt18
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt92
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt19
-rw-r--r--packages/SettingsLib/Spa/gallery/AndroidManifest.xml6
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt21
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt (renamed from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt)4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt2
-rw-r--r--packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml33
-rw-r--r--packages/SettingsLib/res/values/carrierid_icon_overrides.xml32
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java35
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt142
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java11
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java61
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java2
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java140
-rw-r--r--packages/SystemUI/AndroidManifest.xml10
-rw-r--r--packages/SystemUI/res-keyguard/values-land/dimens.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml2
-rw-r--r--packages/SystemUI/res/layout/screen_record_options.xml1
-rw-r--r--packages/SystemUI/res/values-sw600dp-port/dimens.xml1
-rw-r--r--packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml2
-rw-r--r--packages/SystemUI/res/values-sw720dp-port/dimens.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt326
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt111
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java42
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt297
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java87
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt78
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java305
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt351
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt302
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt90
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt109
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java137
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt119
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java63
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt18
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt8
-rw-r--r--services/core/java/com/android/server/SystemService.java12
-rw-r--r--services/core/java/com/android/server/SystemServiceManager.java62
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java4
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java64
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java26
-rw-r--r--services/core/java/com/android/server/am/EventLogTags.logtags12
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java3
-rw-r--r--services/core/java/com/android/server/am/UserController.java106
-rw-r--r--services/core/java/com/android/server/app/GameManagerService.java3
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java15
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java6
-rw-r--r--services/core/java/com/android/server/pm/UserManagerInternal.java37
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java64
-rw-r--r--services/core/java/com/android/server/pm/UserVisibilityMediator.java298
-rw-r--r--services/core/java/com/android/server/policy/SideFpsEventHandler.java2
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java19
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java34
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java17
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java33
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java290
-rw-r--r--services/core/java/com/android/server/wm/EventLogTags.logtags17
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java8
-rw-r--r--services/core/java/com/android/server/wm/Task.java25
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java2
-rw-r--r--services/core/java/com/android/server/wm/Transition.java27
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java9
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/DumpableDumperRule.java91
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java70
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java12
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java246
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java20
-rw-r--r--services/tests/servicestests/src/com/android/server/job/JobStoreTest.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java49
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java8
-rw-r--r--telecomm/OWNERS4
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java2
-rw-r--r--telephony/java/android/telephony/ServiceState.java16
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java10
-rw-r--r--telephony/java/android/telephony/data/DataCallResponse.java45
-rw-r--r--telephony/java/android/telephony/emergency/EmergencyNumber.java11
-rw-r--r--tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java70
-rw-r--r--tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java29
191 files changed, 5386 insertions, 2827 deletions
diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml
index 63e5983401d7..5befa1f1d4d4 100644
--- a/apct-tests/perftests/multiuser/AndroidManifest.xml
+++ b/apct-tests/perftests/multiuser/AndroidManifest.xml
@@ -35,4 +35,8 @@
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.perftests.multiuser"/>
+ <queries>
+ <package android:name="perftests.multiuser.apps.dummyapp" />
+ </queries>
+
</manifest>
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 d9fb318c9335..358f009e8797 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -177,7 +177,7 @@ public class JobSchedulerService extends com.android.server.SystemService
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
private static final long REQUIRE_NETWORK_CONSTRAINT_FOR_NETWORK_JOB_WORK_ITEMS = 241104082L;
- @VisibleForTesting
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static Clock sSystemClock = Clock.systemUTC();
private abstract static class MySimpleClock extends Clock {
@@ -454,6 +454,10 @@ public class JobSchedulerService extends com.android.server.SystemService
runtimeUpdated = true;
}
break;
+ case Constants.KEY_PERSIST_IN_SPLIT_FILES:
+ mConstants.updatePersistingConstantsLocked();
+ mJobs.setUseSplitFiles(mConstants.PERSIST_IN_SPLIT_FILES);
+ break;
default:
if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
&& !concurrencyUpdated) {
@@ -537,6 +541,8 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final String KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS =
"runtime_min_high_priority_guarantee_ms";
+ private static final String KEY_PERSIST_IN_SPLIT_FILES = "persist_in_split_files";
+
private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
@@ -563,6 +569,7 @@ public class JobSchedulerService extends com.android.server.SystemService
public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
@VisibleForTesting
static final long DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS = 5 * MINUTE_IN_MILLIS;
+ static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = false;
private static final boolean DEFAULT_USE_TARE_POLICY = false;
/**
@@ -678,6 +685,12 @@ public class JobSchedulerService extends com.android.server.SystemService
DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS;
/**
+ * Whether to persist jobs in split files (by UID). If false, all persisted jobs will be
+ * saved in a single file.
+ */
+ public boolean PERSIST_IN_SPLIT_FILES = DEFAULT_PERSIST_IN_SPLIT_FILES;
+
+ /**
* If true, use TARE policy for job limiting. If false, use quotas.
*/
public boolean USE_TARE_POLICY = DEFAULT_USE_TARE_POLICY;
@@ -735,6 +748,11 @@ public class JobSchedulerService extends com.android.server.SystemService
DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC);
}
+ private void updatePersistingConstantsLocked() {
+ PERSIST_IN_SPLIT_FILES = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_PERSIST_IN_SPLIT_FILES, DEFAULT_PERSIST_IN_SPLIT_FILES);
+ }
+
private void updatePrefetchConstantsLocked() {
PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
@@ -835,6 +853,8 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
.println();
+ pw.print(KEY_PERSIST_IN_SPLIT_FILES, PERSIST_IN_SPLIT_FILES).println();
+
pw.print(Settings.Global.ENABLE_TARE, USE_TARE_POLICY).println();
pw.decreaseIndent();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 68cb049af758..2f94705f01c2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -40,6 +40,7 @@ import android.util.AtomicFile;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SystemConfigFileCommitEventLogger;
import android.util.Xml;
@@ -89,6 +90,8 @@ public final class JobStore {
/** Threshold to adjust how often we want to write to the db. */
private static final long JOB_PERSIST_DELAY = 2000L;
+ private static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
+ private static final int ALL_UIDS = -1;
final Object mLock;
final Object mWriteScheduleLock; // used solely for invariants around write scheduling
@@ -105,13 +108,20 @@ public final class JobStore {
@GuardedBy("mWriteScheduleLock")
private boolean mWriteInProgress;
+ @GuardedBy("mWriteScheduleLock")
+ private boolean mSplitFileMigrationNeeded;
+
private static final Object sSingletonLock = new Object();
private final SystemConfigFileCommitEventLogger mEventLogger;
private final AtomicFile mJobsFile;
+ private final File mJobFileDirectory;
+ private final SparseBooleanArray mPendingJobWriteUids = new SparseBooleanArray();
/** Handler backed by IoThread for writing to disk. */
private final Handler mIoHandler = IoThread.getHandler();
private static JobStore sSingleton;
+ private boolean mUseSplitFiles = JobSchedulerService.Constants.DEFAULT_PERSIST_IN_SPLIT_FILES;
+
private JobStorePersistStats mPersistInfo = new JobStorePersistStats();
/** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
@@ -144,10 +154,10 @@ public final class JobStore {
mContext = context;
File systemDir = new File(dataDir, "system");
- File jobDir = new File(systemDir, "job");
- jobDir.mkdirs();
+ mJobFileDirectory = new File(systemDir, "job");
+ mJobFileDirectory.mkdirs();
mEventLogger = new SystemConfigFileCommitEventLogger("jobs");
- mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"), mEventLogger);
+ mJobsFile = createJobFile(new File(mJobFileDirectory, "jobs.xml"));
mJobSet = new JobSet();
@@ -162,12 +172,21 @@ public final class JobStore {
// an incorrect historical timestamp. That's fine; at worst we'll reboot with
// a *correct* timestamp, see a bunch of overdue jobs, and run them; then
// settle into normal operation.
- mXmlTimestamp = mJobsFile.getLastModifiedTime();
+ mXmlTimestamp = mJobsFile.exists()
+ ? mJobsFile.getLastModifiedTime() : mJobFileDirectory.lastModified();
mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
readJobMapFromDisk(mJobSet, mRtcGood);
}
+ private AtomicFile createJobFile(String baseName) {
+ return createJobFile(new File(mJobFileDirectory, baseName + ".xml"));
+ }
+
+ private AtomicFile createJobFile(File file) {
+ return new AtomicFile(file, mEventLogger);
+ }
+
public boolean jobTimesInflatedValid() {
return mRtcGood;
}
@@ -211,6 +230,7 @@ public final class JobStore {
public void add(JobStatus jobStatus) {
mJobSet.add(jobStatus);
if (jobStatus.isPersisted()) {
+ mPendingJobWriteUids.put(jobStatus.getUid(), true);
maybeWriteStatusToDiskAsync();
}
if (DEBUG) {
@@ -224,6 +244,9 @@ public final class JobStore {
@VisibleForTesting
public void addForTesting(JobStatus jobStatus) {
mJobSet.add(jobStatus);
+ if (jobStatus.isPersisted()) {
+ mPendingJobWriteUids.put(jobStatus.getUid(), true);
+ }
}
boolean containsJob(JobStatus jobStatus) {
@@ -257,12 +280,24 @@ public final class JobStore {
return false;
}
if (removeFromPersisted && jobStatus.isPersisted()) {
+ mPendingJobWriteUids.put(jobStatus.getUid(), true);
maybeWriteStatusToDiskAsync();
}
return removed;
}
/**
+ * Like {@link #remove(JobStatus, boolean)}, but doesn't schedule a disk write.
+ */
+ @VisibleForTesting
+ public void removeForTesting(JobStatus jobStatus) {
+ mJobSet.remove(jobStatus);
+ if (jobStatus.isPersisted()) {
+ mPendingJobWriteUids.put(jobStatus.getUid(), true);
+ }
+ }
+
+ /**
* Remove the jobs of users not specified in the keepUserIds.
* @param keepUserIds Array of User IDs whose jobs should be kept and not removed.
*/
@@ -273,6 +308,7 @@ public final class JobStore {
@VisibleForTesting
public void clear() {
mJobSet.clear();
+ mPendingJobWriteUids.put(ALL_UIDS, true);
maybeWriteStatusToDiskAsync();
}
@@ -282,6 +318,36 @@ public final class JobStore {
@VisibleForTesting
public void clearForTesting() {
mJobSet.clear();
+ mPendingJobWriteUids.put(ALL_UIDS, true);
+ }
+
+ void setUseSplitFiles(boolean useSplitFiles) {
+ synchronized (mLock) {
+ if (mUseSplitFiles != useSplitFiles) {
+ mUseSplitFiles = useSplitFiles;
+ migrateJobFilesAsync();
+ }
+ }
+ }
+
+ /**
+ * The same as above but does not schedule writing. This makes perf benchmarks more stable.
+ */
+ @VisibleForTesting
+ public void setUseSplitFilesForTesting(boolean useSplitFiles) {
+ final boolean changed;
+ synchronized (mLock) {
+ changed = mUseSplitFiles != useSplitFiles;
+ if (changed) {
+ mUseSplitFiles = useSplitFiles;
+ mPendingJobWriteUids.put(ALL_UIDS, true);
+ }
+ }
+ if (changed) {
+ synchronized (mWriteScheduleLock) {
+ mSplitFileMigrationNeeded = true;
+ }
+ }
}
/**
@@ -352,6 +418,16 @@ public final class JobStore {
private static final String XML_TAG_ONEOFF = "one-off";
private static final String XML_TAG_EXTRAS = "extras";
+ private void migrateJobFilesAsync() {
+ synchronized (mLock) {
+ mPendingJobWriteUids.put(ALL_UIDS, true);
+ }
+ synchronized (mWriteScheduleLock) {
+ mSplitFileMigrationNeeded = true;
+ maybeWriteStatusToDiskAsync();
+ }
+ }
+
/**
* Every time the state changes we write all the jobs in one swath, instead of trying to
* track incremental changes.
@@ -449,10 +525,38 @@ public final class JobStore {
* NOTE: This Runnable locks on mLock
*/
private final Runnable mWriteRunnable = new Runnable() {
+ private final SparseArray<AtomicFile> mJobFiles = new SparseArray<>();
+ private final CopyConsumer mPersistedJobCopier = new CopyConsumer();
+
+ class CopyConsumer implements Consumer<JobStatus> {
+ private final SparseArray<List<JobStatus>> mJobStoreCopy = new SparseArray<>();
+ private boolean mCopyAllJobs;
+
+ private void prepare() {
+ mCopyAllJobs = !mUseSplitFiles || mPendingJobWriteUids.get(ALL_UIDS);
+ }
+
+ @Override
+ public void accept(JobStatus jobStatus) {
+ final int uid = mUseSplitFiles ? jobStatus.getUid() : ALL_UIDS;
+ if (jobStatus.isPersisted() && (mCopyAllJobs || mPendingJobWriteUids.get(uid))) {
+ List<JobStatus> uidJobList = mJobStoreCopy.get(uid);
+ if (uidJobList == null) {
+ uidJobList = new ArrayList<>();
+ mJobStoreCopy.put(uid, uidJobList);
+ }
+ uidJobList.add(new JobStatus(jobStatus));
+ }
+ }
+
+ private void reset() {
+ mJobStoreCopy.clear();
+ }
+ }
+
@Override
public void run() {
final long startElapsed = sElapsedRealtimeClock.millis();
- final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
// Intentionally allow new scheduling of a write operation *before* we clone
// the job set. If we reset it to false after cloning, there's a window in
// which no new write will be scheduled but mLock is not held, i.e. a new
@@ -469,31 +573,73 @@ public final class JobStore {
}
mWriteInProgress = true;
}
+ final boolean useSplitFiles;
synchronized (mLock) {
// Clone the jobs so we can release the lock before writing.
- mJobSet.forEachJob(null, (job) -> {
- if (job.isPersisted()) {
- storeCopy.add(new JobStatus(job));
+ useSplitFiles = mUseSplitFiles;
+ mPersistedJobCopier.prepare();
+ mJobSet.forEachJob(null, mPersistedJobCopier);
+ mPendingJobWriteUids.clear();
+ }
+ mPersistInfo.countAllJobsSaved = 0;
+ mPersistInfo.countSystemServerJobsSaved = 0;
+ mPersistInfo.countSystemSyncManagerJobsSaved = 0;
+ for (int i = mPersistedJobCopier.mJobStoreCopy.size() - 1; i >= 0; --i) {
+ AtomicFile file;
+ if (useSplitFiles) {
+ final int uid = mPersistedJobCopier.mJobStoreCopy.keyAt(i);
+ file = mJobFiles.get(uid);
+ if (file == null) {
+ file = createJobFile(JOB_FILE_SPLIT_PREFIX + uid);
+ mJobFiles.put(uid, file);
}
- });
+ } else {
+ file = mJobsFile;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Writing for " + mPersistedJobCopier.mJobStoreCopy.keyAt(i)
+ + " to " + file.getBaseFile().getName() + ": "
+ + mPersistedJobCopier.mJobStoreCopy.valueAt(i).size() + " jobs");
+ }
+ writeJobsMapImpl(file, mPersistedJobCopier.mJobStoreCopy.valueAt(i));
}
- writeJobsMapImpl(storeCopy);
if (DEBUG) {
Slog.v(TAG, "Finished writing, took " + (sElapsedRealtimeClock.millis()
- startElapsed) + "ms");
}
+ mPersistedJobCopier.reset();
+ if (!useSplitFiles) {
+ mJobFiles.clear();
+ }
+ // Update the last modified time of the directory to aid in RTC time verification
+ // (see the JobStore constructor).
+ mJobFileDirectory.setLastModified(sSystemClock.millis());
synchronized (mWriteScheduleLock) {
+ if (mSplitFileMigrationNeeded) {
+ final File[] files = mJobFileDirectory.listFiles();
+ for (File file : files) {
+ if (useSplitFiles) {
+ if (!file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
+ // Delete the now unused file so there's no confusion in the future.
+ file.delete();
+ }
+ } else if (file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
+ // Delete the now unused file so there's no confusion in the future.
+ file.delete();
+ }
+ }
+ }
mWriteInProgress = false;
mWriteScheduleLock.notifyAll();
}
}
- private void writeJobsMapImpl(List<JobStatus> jobList) {
+ private void writeJobsMapImpl(@NonNull AtomicFile file, @NonNull List<JobStatus> jobList) {
int numJobs = 0;
int numSystemJobs = 0;
int numSyncJobs = 0;
mEventLogger.setStartTime(SystemClock.uptimeMillis());
- try (FileOutputStream fos = mJobsFile.startWrite()) {
+ try (FileOutputStream fos = file.startWrite()) {
TypedXmlSerializer out = Xml.resolveSerializer(fos);
out.startDocument(null, true);
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
@@ -523,7 +669,7 @@ public final class JobStore {
out.endTag(null, "job-info");
out.endDocument();
- mJobsFile.finishWrite(fos);
+ file.finishWrite(fos);
} catch (IOException e) {
if (DEBUG) {
Slog.v(TAG, "Error writing out job data.", e);
@@ -533,9 +679,9 @@ public final class JobStore {
Slog.d(TAG, "Error persisting bundle.", e);
}
} finally {
- mPersistInfo.countAllJobsSaved = numJobs;
- mPersistInfo.countSystemServerJobsSaved = numSystemJobs;
- mPersistInfo.countSystemSyncManagerJobsSaved = numSyncJobs;
+ mPersistInfo.countAllJobsSaved += numJobs;
+ mPersistInfo.countSystemServerJobsSaved += numSystemJobs;
+ mPersistInfo.countSystemSyncManagerJobsSaved += numSyncJobs;
}
}
@@ -720,49 +866,82 @@ public final class JobStore {
@Override
public void run() {
+ if (!mJobFileDirectory.isDirectory()) {
+ Slog.wtf(TAG, "jobs directory isn't a directory O.O");
+ mJobFileDirectory.mkdirs();
+ return;
+ }
+
int numJobs = 0;
int numSystemJobs = 0;
int numSyncJobs = 0;
List<JobStatus> jobs;
- try (FileInputStream fis = mJobsFile.openRead()) {
- synchronized (mLock) {
- jobs = readJobMapImpl(fis, rtcGood);
- if (jobs != null) {
- long now = sElapsedRealtimeClock.millis();
- for (int i=0; i<jobs.size(); i++) {
- JobStatus js = jobs.get(i);
- js.prepareLocked();
- js.enqueueTime = now;
- this.jobSet.add(js);
-
- numJobs++;
- if (js.getUid() == Process.SYSTEM_UID) {
- numSystemJobs++;
- if (isSyncJob(js)) {
- numSyncJobs++;
+ final File[] files;
+ try {
+ files = mJobFileDirectory.listFiles();
+ } catch (SecurityException e) {
+ Slog.wtf(TAG, "Not allowed to read job file directory", e);
+ return;
+ }
+ if (files == null) {
+ Slog.wtfStack(TAG, "Couldn't get job file list");
+ return;
+ }
+ boolean needFileMigration = false;
+ long now = sElapsedRealtimeClock.millis();
+ for (File file : files) {
+ final AtomicFile aFile = createJobFile(file);
+ try (FileInputStream fis = aFile.openRead()) {
+ synchronized (mLock) {
+ jobs = readJobMapImpl(fis, rtcGood);
+ if (jobs != null) {
+ for (int i = 0; i < jobs.size(); i++) {
+ JobStatus js = jobs.get(i);
+ js.prepareLocked();
+ js.enqueueTime = now;
+ this.jobSet.add(js);
+
+ numJobs++;
+ if (js.getUid() == Process.SYSTEM_UID) {
+ numSystemJobs++;
+ if (isSyncJob(js)) {
+ numSyncJobs++;
+ }
}
}
}
}
+ } catch (FileNotFoundException e) {
+ // mJobFileDirectory.listFiles() gave us this file...why can't we find it???
+ Slog.e(TAG, "Could not find jobs file: " + file.getName());
+ } catch (XmlPullParserException | IOException e) {
+ Slog.wtf(TAG, "Error in " + file.getName(), e);
+ } catch (Exception e) {
+ // Crashing at this point would result in a boot loop, so live with a general
+ // Exception for system stability's sake.
+ Slog.wtf(TAG, "Unexpected exception", e);
}
- } catch (FileNotFoundException e) {
- if (DEBUG) {
- Slog.d(TAG, "Could not find jobs file, probably there was nothing to load.");
- }
- } catch (XmlPullParserException | IOException e) {
- Slog.wtf(TAG, "Error jobstore xml.", e);
- } catch (Exception e) {
- // Crashing at this point would result in a boot loop, so live with a general
- // Exception for system stability's sake.
- Slog.wtf(TAG, "Unexpected exception", e);
- } finally {
- if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
- mPersistInfo.countAllJobsLoaded = numJobs;
- mPersistInfo.countSystemServerJobsLoaded = numSystemJobs;
- mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs;
+ if (mUseSplitFiles) {
+ if (!file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
+ // We're supposed to be using the split file architecture, but we still have
+ // the old job file around. Fully migrate and remove the old file.
+ needFileMigration = true;
+ }
+ } else if (file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
+ // We're supposed to be using the legacy single file architecture, but we still
+ // have some job split files around. Fully migrate and remove the split files.
+ needFileMigration = true;
}
}
+ if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
+ mPersistInfo.countAllJobsLoaded = numJobs;
+ mPersistInfo.countSystemServerJobsLoaded = numSystemJobs;
+ mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs;
+ }
Slog.i(TAG, "Read " + numJobs + " jobs");
+ if (needFileMigration) {
+ migrateJobFilesAsync();
+ }
}
private List<JobStatus> readJobMapImpl(InputStream fis, boolean rtcIsGood)
diff --git a/core/api/current.txt b/core/api/current.txt
index bba189f229d9..ee7cdefa77a1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -32542,7 +32542,7 @@ package android.os {
method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.QUERY_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle);
method public android.os.Bundle getUserRestrictions();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
- method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}) public java.util.List<android.os.UserHandle> getVisibleUsers();
+ method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}) public java.util.Set<android.os.UserHandle> getVisibleUsers();
method public boolean hasUserRestriction(String);
method public boolean isDemoUser();
method public static boolean isHeadlessSystemUserMode();
@@ -35801,6 +35801,7 @@ package android.provider {
field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
field public static final String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS";
field public static final String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS";
+ field public static final String ACTION_MEMTAG_SETTINGS = "android.settings.MEMTAG_SETTINGS";
field public static final String ACTION_NETWORK_OPERATOR_SETTINGS = "android.settings.NETWORK_OPERATOR_SETTINGS";
field public static final String ACTION_NFCSHARING_SETTINGS = "android.settings.NFCSHARING_SETTINGS";
field public static final String ACTION_NFC_PAYMENT_SETTINGS = "android.settings.NFC_PAYMENT_SETTINGS";
@@ -44095,8 +44096,8 @@ package android.telephony {
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED = 3; // 0x3
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED = 7; // 0x7
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR = 8; // 0x8
+ field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED = 13; // 0xd
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED = 10; // 0xa
- field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13; // 0xd
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB = 14; // 0xe
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5; // 0x5
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 7103806fa751..8ec313ecdd09 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -30,7 +30,7 @@ per-file ProfilerInfo* = file:/services/core/java/com/android/server/am/OWNERS
per-file Service* = file:/services/core/java/com/android/server/am/OWNERS
per-file SystemServiceRegistry.java = file:/services/core/java/com/android/server/am/OWNERS
per-file *UserSwitchObserver* = file:/services/core/java/com/android/server/am/OWNERS
-per-file UiAutomation* = file:/services/accessibility/OWNERS
+per-file *UiAutomation* = file:/services/accessibility/OWNERS
per-file GameManager* = file:/GAME_MANAGER_OWNERS
per-file GameMode* = file:/GAME_MANAGER_OWNERS
per-file GameState* = file:/GAME_MANAGER_OWNERS
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 30a6c311bd1e..ee14708c38a8 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -176,7 +176,6 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
/** Write to Parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeStrongBinder(mClient.asBinder());
final boolean writeActivityToken = mActivityToken != null;
dest.writeBoolean(writeActivityToken);
if (writeActivityToken) {
@@ -192,7 +191,6 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
/** Read from Parcel. */
private ClientTransaction(Parcel in) {
- mClient = (IApplicationThread) in.readStrongBinder();
final boolean readActivityToken = in.readBoolean();
if (readActivityToken) {
mActivityToken = in.readStrongBinder();
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 63306612fdaf..1929a4d562d4 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -536,8 +536,8 @@ public final class BinderProxy implements IBinder {
mWarnOnBlocking = false;
warnOnBlocking = false;
- if (Build.IS_USERDEBUG) {
- // Log this as a WTF on userdebug builds.
+ if (Build.IS_USERDEBUG || Build.IS_ENG) {
+ // Log this as a WTF on userdebug and eng builds.
Log.wtf(Binder.TAG,
"Outgoing transactions from this process must be FLAG_ONEWAY",
new Throwable());
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 8eaa5ad7fe94..a887f2a6ef29 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -130,7 +130,7 @@ interface IUserManager {
boolean isUserRunning(int userId);
boolean isUserForeground(int userId);
boolean isUserVisible(int userId);
- List<UserHandle> getVisibleUsers();
+ int[] getVisibleUsers();
boolean isUserNameSet(int userId);
boolean hasRestrictedProfiles(int userId);
boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3d20d6373ae2..a38d9daee74a 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2904,12 +2904,19 @@ public class UserManager {
*/
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS})
- public @NonNull List<UserHandle> getVisibleUsers() {
+ public @NonNull Set<UserHandle> getVisibleUsers() {
+ ArraySet<UserHandle> result = new ArraySet<>();
try {
- return mService.getVisibleUsers();
+ int[] visibleUserIds = mService.getVisibleUsers();
+ if (visibleUserIds != null) {
+ for (int userId : visibleUserIds) {
+ result.add(UserHandle.of(userId));
+ }
+ }
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
+ return result;
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index fab6f7b97790..5865437d855c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -675,6 +675,22 @@ public final class Settings {
"android.settings.WIFI_SETTINGS";
/**
+ * Activity Action: Show settings to allow configuration of MTE.
+ * <p>
+ * Memory Tagging Extension (MTE) is a CPU extension that allows to protect against certain
+ * classes of security problems at a small runtime performance cost overhead.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MEMTAG_SETTINGS =
+ "android.settings.MEMTAG_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of a static IP
* address for Wi-Fi.
* <p>
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index 47b16a351806..d2a4ae282061 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -55,6 +55,20 @@ public abstract class ControlsProviderService extends Service {
"android.service.controls.ControlsProviderService";
/**
+ * Manifest metadata to show a custom embedded activity as part of device controls.
+ *
+ * The value of this metadata must be the {@link ComponentName} as a string of an activity in
+ * the same package that will be launched as part of a TaskView.
+ *
+ * The activity must be exported, enabled and protected by
+ * {@link Manifest.permission.BIND_CONTROLS}.
+ *
+ * @hide
+ */
+ public static final String META_DATA_PANEL_ACTIVITY =
+ "android.service.controls.META_DATA_PANEL_ACTIVITY";
+
+ /**
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 37fc9f288c3e..104570d03b42 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -575,6 +575,7 @@ public abstract class WallpaperService extends Service {
*/
public void reportEngineShown(boolean waitForEngineShown) {
if (mIWallpaperEngine.mShownReported) return;
+ Trace.beginSection("WPMS.reportEngineShown-" + waitForEngineShown);
Log.d(TAG, "reportEngineShown: shouldWait=" + waitForEngineShown);
if (!waitForEngineShown) {
Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN);
@@ -587,6 +588,7 @@ public abstract class WallpaperService extends Service {
mCaller.sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(5));
}
}
+ Trace.endSection();
}
/**
@@ -1259,7 +1261,9 @@ public abstract class WallpaperService extends Service {
didSurface = true;
if (DEBUG) Log.v(TAG, "onSurfaceCreated("
+ mSurfaceHolder + "): " + this);
+ Trace.beginSection("WPMS.Engine.onSurfaceCreated");
onSurfaceCreated(mSurfaceHolder);
+ Trace.endSection();
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
@@ -1285,8 +1289,10 @@ public abstract class WallpaperService extends Service {
+ ", " + mCurWidth + ", " + mCurHeight
+ "): " + this);
didSurface = true;
+ Trace.beginSection("WPMS.Engine.onSurfaceChanged");
onSurfaceChanged(mSurfaceHolder, mFormat,
mCurWidth, mCurHeight);
+ Trace.endSection();
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
@@ -1303,11 +1309,15 @@ public abstract class WallpaperService extends Service {
if (DEBUG) {
Log.v(TAG, "dispatching insets=" + windowInsets);
}
+ Trace.beginSection("WPMS.Engine.onApplyWindowInsets");
onApplyWindowInsets(windowInsets);
+ Trace.endSection();
}
if (redrawNeeded) {
+ Trace.beginSection("WPMS.Engine.onSurfaceRedrawNeeded");
onSurfaceRedrawNeeded(mSurfaceHolder);
+ Trace.endSection();
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
@@ -1332,11 +1342,15 @@ public abstract class WallpaperService extends Service {
// the state to get them to notice.
if (DEBUG) Log.v(TAG, "onVisibilityChanged(true) at surface: "
+ this);
+ Trace.beginSection("WPMS.Engine.onVisibilityChanged-true");
onVisibilityChanged(true);
+ Trace.endSection();
}
if (DEBUG) Log.v(TAG, "onVisibilityChanged(false) at surface: "
+ this);
+ Trace.beginSection("WPMS.Engine.onVisibilityChanged-false");
onVisibilityChanged(false);
+ Trace.endSection();
}
} finally {
mIsCreating = false;
@@ -1421,12 +1435,16 @@ public abstract class WallpaperService extends Service {
mDisplayState = mDisplay.getState();
if (DEBUG) Log.v(TAG, "onCreate(): " + this);
+ Trace.beginSection("WPMS.Engine.onCreate");
onCreate(mSurfaceHolder);
+ Trace.endSection();
mInitializing = false;
mReportedVisible = false;
+ Trace.beginSection("WPMS.Engine.updateSurface");
updateSurface(false, false, false);
+ Trace.endSection();
}
/**
@@ -2236,14 +2254,15 @@ public abstract class WallpaperService extends Service {
public void reportShown() {
if (!mShownReported) {
mShownReported = true;
+ Trace.beginSection("WPMS.mConnection.engineShown");
try {
mConnection.engineShown(this);
Log.d(TAG, "Wallpaper has updated the surface:"
+ mWallpaperManager.getWallpaperInfo());
} catch (RemoteException e) {
Log.w(TAG, "Wallpaper host disappeared", e);
- return;
}
+ Trace.endSection();
}
}
@@ -2285,6 +2304,27 @@ public abstract class WallpaperService extends Service {
return mEngine == null ? null : SurfaceControl.mirrorSurface(mEngine.mSurfaceControl);
}
+ private void doAttachEngine() {
+ Trace.beginSection("WPMS.onCreateEngine");
+ Engine engine = onCreateEngine();
+ Trace.endSection();
+ mEngine = engine;
+ Trace.beginSection("WPMS.mConnection.attachEngine-" + mDisplayId);
+ try {
+ mConnection.attachEngine(this, mDisplayId);
+ } catch (RemoteException e) {
+ engine.detach();
+ Log.w(TAG, "Wallpaper host disappeared", e);
+ return;
+ } finally {
+ Trace.endSection();
+ }
+ mActiveEngines.add(engine);
+ Trace.beginSection("WPMS.engine.attach");
+ engine.attach(this);
+ Trace.endSection();
+ }
+
private void doDetachEngine() {
mActiveEngines.remove(mEngine);
mEngine.detach();
@@ -2310,21 +2350,15 @@ public abstract class WallpaperService extends Service {
}
switch (message.what) {
case DO_ATTACH: {
- Engine engine = onCreateEngine();
- mEngine = engine;
- try {
- mConnection.attachEngine(this, mDisplayId);
- } catch (RemoteException e) {
- engine.detach();
- Log.w(TAG, "Wallpaper host disappeared", e);
- return;
- }
- mActiveEngines.add(engine);
- engine.attach(this);
+ Trace.beginSection("WPMS.DO_ATTACH");
+ doAttachEngine();
+ Trace.endSection();
return;
}
case DO_DETACH: {
+ Trace.beginSection("WPMS.DO_DETACH");
doDetachEngine();
+ Trace.endSection();
return;
}
case DO_SET_DESIRED_SIZE: {
@@ -2405,7 +2439,9 @@ public abstract class WallpaperService extends Service {
}
} break;
case MSG_REPORT_SHOWN: {
+ Trace.beginSection("WPMS.MSG_REPORT_SHOWN");
reportShown();
+ Trace.endSection();
} break;
default :
Log.w(TAG, "Unknown message type " + message.what);
@@ -2429,8 +2465,10 @@ public abstract class WallpaperService extends Service {
public void attach(IWallpaperConnection conn, IBinder windowToken,
int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
int displayId, @SetWallpaperFlags int which) {
+ Trace.beginSection("WPMS.ServiceWrapper.attach");
mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken,
windowType, isPreview, reqWidth, reqHeight, padding, displayId);
+ Trace.endSection();
}
@Override
@@ -2441,16 +2479,20 @@ public abstract class WallpaperService extends Service {
@Override
public void onCreate() {
+ Trace.beginSection("WPMS.onCreate");
super.onCreate();
+ Trace.endSection();
}
@Override
public void onDestroy() {
+ Trace.beginSection("WPMS.onDestroy");
super.onDestroy();
for (int i=0; i<mActiveEngines.size(); i++) {
mActiveEngines.get(i).detach();
}
mActiveEngines.clear();
+ Trace.endSection();
}
/**
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index 9b91cf2e9db6..a25e0351df83 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -89,8 +89,6 @@ public final class BackNavigationInfo implements Parcelable {
@Nullable
private final IOnBackInvokedCallback mOnBackInvokedCallback;
private final boolean mPrepareRemoteAnimation;
- @Nullable
- private WindowContainerToken mDepartingWindowContainerToken;
/**
* Create a new {@link BackNavigationInfo} instance.
@@ -100,20 +98,15 @@ public final class BackNavigationInfo implements Parcelable {
* back preview.
* @param onBackInvokedCallback The back callback registered by the current top level window.
* @param departingWindowContainerToken The {@link WindowContainerToken} of departing window.
- * @param isPrepareRemoteAnimation Return whether the core is preparing a back gesture
- * animation, if true, the caller of startBackNavigation should
- * be expected to receive an animation start callback.
*/
private BackNavigationInfo(@BackTargetType int type,
@Nullable RemoteCallback onBackNavigationDone,
@Nullable IOnBackInvokedCallback onBackInvokedCallback,
- boolean isPrepareRemoteAnimation,
- @Nullable WindowContainerToken departingWindowContainerToken) {
+ boolean isPrepareRemoteAnimation) {
mType = type;
mOnBackNavigationDone = onBackNavigationDone;
mOnBackInvokedCallback = onBackInvokedCallback;
mPrepareRemoteAnimation = isPrepareRemoteAnimation;
- mDepartingWindowContainerToken = departingWindowContainerToken;
}
private BackNavigationInfo(@NonNull Parcel in) {
@@ -121,7 +114,6 @@ public final class BackNavigationInfo implements Parcelable {
mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR);
mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
mPrepareRemoteAnimation = in.readBoolean();
- mDepartingWindowContainerToken = in.readTypedObject(WindowContainerToken.CREATOR);
}
@Override
@@ -130,7 +122,6 @@ public final class BackNavigationInfo implements Parcelable {
dest.writeTypedObject(mOnBackNavigationDone, flags);
dest.writeStrongInterface(mOnBackInvokedCallback);
dest.writeBoolean(mPrepareRemoteAnimation);
- dest.writeTypedObject(mDepartingWindowContainerToken, flags);
}
/**
@@ -164,18 +155,6 @@ public final class BackNavigationInfo implements Parcelable {
}
/**
- * Returns the {@link WindowContainerToken} of the highest container in the hierarchy being
- * removed.
- * <p>
- * For example, if an Activity is the last one of its Task, the Task's token will be given.
- * Otherwise, it will be the Activity's token.
- */
- @Nullable
- public WindowContainerToken getDepartingWindowContainerToken() {
- return mDepartingWindowContainerToken;
- }
-
- /**
* Callback to be called when the back preview is finished in order to notify the server that
* it can clean up the resources created for the animation.
*
@@ -212,7 +191,6 @@ public final class BackNavigationInfo implements Parcelable {
+ "mType=" + typeToString(mType) + " (" + mType + ")"
+ ", mOnBackNavigationDone=" + mOnBackNavigationDone
+ ", mOnBackInvokedCallback=" + mOnBackInvokedCallback
- + ", mWindowContainerToken=" + mDepartingWindowContainerToken
+ '}';
}
@@ -248,8 +226,6 @@ public final class BackNavigationInfo implements Parcelable {
@Nullable
private IOnBackInvokedCallback mOnBackInvokedCallback = null;
private boolean mPrepareRemoteAnimation;
- @Nullable
- private WindowContainerToken mDepartingWindowContainerToken = null;
/**
* @see BackNavigationInfo#getType()
@@ -285,20 +261,12 @@ public final class BackNavigationInfo implements Parcelable {
}
/**
- * @see BackNavigationInfo#getDepartingWindowContainerToken()
- */
- public void setDepartingWCT(@NonNull WindowContainerToken windowContainerToken) {
- mDepartingWindowContainerToken = windowContainerToken;
- }
-
- /**
* Builds and returns an instance of {@link BackNavigationInfo}
*/
public BackNavigationInfo build() {
return new BackNavigationInfo(mType, mOnBackNavigationDone,
mOnBackInvokedCallback,
- mPrepareRemoteAnimation,
- mDepartingWindowContainerToken);
+ mPrepareRemoteAnimation);
}
}
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 0956a71bd92d..c2da638aca8d 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -138,8 +138,11 @@ public final class TransitionInfo implements Parcelable {
/** The container is a system window, excluding wallpaper and input-method. */
public static final int FLAG_IS_SYSTEM_WINDOW = 1 << 16;
+ /** The window was animated by back gesture. */
+ public static final int FLAG_BACK_GESTURE_ANIMATED = 1 << 17;
+
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
- public static final int FLAG_FIRST_CUSTOM = 1 << 17;
+ public static final int FLAG_FIRST_CUSTOM = 1 << 18;
/** The change belongs to a window that won't contain activities. */
public static final int FLAGS_IS_NON_APP_WINDOW =
@@ -165,6 +168,7 @@ public final class TransitionInfo implements Parcelable {
FLAG_IS_BEHIND_STARTING_WINDOW,
FLAG_IS_OCCLUDED,
FLAG_IS_SYSTEM_WINDOW,
+ FLAG_BACK_GESTURE_ANIMATED,
FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
@@ -380,6 +384,9 @@ public final class TransitionInfo implements Parcelable {
if ((flags & FLAG_IS_SYSTEM_WINDOW) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("FLAG_IS_SYSTEM_WINDOW");
}
+ if ((flags & FLAG_BACK_GESTURE_ANIMATED) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|").append("FLAG_BACK_GESTURE_ANIMATED");
+ }
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index d9ca16ea8748..0798110134f8 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -19,6 +19,7 @@
#define LOG_NDEBUG 1
#include <android-base/macros.h>
+#include <android-base/parsebool.h>
#include <android-base/properties.h>
#include <android/graphics/jni_runtime.h>
#include <android_runtime/AndroidRuntime.h>
@@ -52,6 +53,8 @@
using namespace android;
using android::base::GetBoolProperty;
using android::base::GetProperty;
+using android::base::ParseBool;
+using android::base::ParseBoolResult;
extern int register_android_os_Binder(JNIEnv* env);
extern int register_android_os_Process(JNIEnv* env);
@@ -703,17 +706,24 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p
// Read if we are using the profile configuration, do this at the start since the last ART args
// take precedence.
- property_get("dalvik.vm.profilebootclasspath", propBuf, "");
- std::string profile_boot_class_path_flag = propBuf;
- // Empty means the property is unset and we should default to the phenotype property.
- // The possible values are {"true", "false", ""}
- if (profile_boot_class_path_flag.empty()) {
- profile_boot_class_path_flag = server_configurable_flags::GetServerConfigurableFlag(
- RUNTIME_NATIVE_BOOT_NAMESPACE,
- PROFILE_BOOT_CLASS_PATH,
- /*default_value=*/ "");
+ std::string profile_boot_class_path_flag =
+ server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE,
+ PROFILE_BOOT_CLASS_PATH,
+ /*default_value=*/"");
+ bool profile_boot_class_path;
+ switch (ParseBool(profile_boot_class_path_flag)) {
+ case ParseBoolResult::kError:
+ // Default to the system property.
+ profile_boot_class_path =
+ GetBoolProperty("dalvik.vm.profilebootclasspath", /*default_value=*/false);
+ break;
+ case ParseBoolResult::kTrue:
+ profile_boot_class_path = true;
+ break;
+ case ParseBoolResult::kFalse:
+ profile_boot_class_path = false;
+ break;
}
- const bool profile_boot_class_path = (profile_boot_class_path_flag == "true");
if (profile_boot_class_path) {
addOption("-Xcompiler-option");
addOption("--count-hotness-in-compiled-code");
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 06d7cddf3148..c3623a28c17a 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -210,7 +210,7 @@ public final class TunerSessionTest {
mTunerSessions[0].setMuted(/* mute= */ false);
- assertWithMessage("Session mute state after setting muted %s", false)
+ assertWithMessage("Session mute state after setting unmuted")
.that(mTunerSessions[0].isMuted()).isFalse();
}
@@ -220,7 +220,7 @@ public final class TunerSessionTest {
mTunerSessions[0].setMuted(/* mute= */ true);
- assertWithMessage("Session mute state after setting muted %s", true)
+ assertWithMessage("Session mute state after setting muted")
.that(mTunerSessions[0].isMuted()).isTrue();
}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java
index 392e140f24e0..ad1b8727d8a7 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java
@@ -22,6 +22,7 @@ import android.hardware.broadcastradio.V2_0.VendorKeyValue;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
+import android.util.ArrayMap;
import java.util.ArrayList;
import java.util.HashMap;
@@ -32,14 +33,33 @@ final class TestUtils {
throw new UnsupportedOperationException("TestUtils class is noninstantiable");
}
+ static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
+ return new RadioManager.ProgramInfo(selector,
+ selector.getPrimaryId(), selector.getPrimaryId(), /* relatedContents= */ null,
+ /* infoFlags= */ 0, signalQuality,
+ new RadioMetadata.Builder().build(), new ArrayMap<>());
+ }
+
static RadioManager.ProgramInfo makeProgramInfo(int programType,
ProgramSelector.Identifier identifier, int signalQuality) {
// Note: If you set new fields, check if programInfoToHal() needs to be updated as well.
- return new RadioManager.ProgramInfo(new ProgramSelector(programType, identifier, null,
- null), null, null, null, 0, signalQuality, new RadioMetadata.Builder().build(),
+ return new RadioManager.ProgramInfo(makeProgramSelector(programType, identifier), null,
+ null, null, 0, signalQuality, new RadioMetadata.Builder().build(),
new HashMap<String, String>());
}
+ static ProgramSelector makeFmSelector(long freq) {
+ return makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM,
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+ freq));
+ }
+
+ static ProgramSelector makeProgramSelector(int programType,
+ ProgramSelector.Identifier identifier) {
+ return new ProgramSelector(programType, identifier, /* secondaryIds= */ null,
+ /* vendorIds= */ null);
+ }
+
static ProgramInfo programInfoToHal(RadioManager.ProgramInfo info) {
// Note that because Convert does not by design provide functions for all conversions, this
// function only copies fields that are set by makeProgramInfo().
@@ -57,10 +77,22 @@ final class TestUtils {
android.hardware.broadcastradio.V2_0.ProgramSelector halSelector =
new android.hardware.broadcastradio.V2_0.ProgramSelector();
halSelector.primaryId = halId;
- halSelector.secondaryIds = new ArrayList<>();
+ halSelector.secondaryIds = new ArrayList<ProgramIdentifier>();
return halSelector;
}
+ static ProgramInfo makeHalProgramInfo(
+ android.hardware.broadcastradio.V2_0.ProgramSelector hwSel, int hwSignalQuality) {
+ ProgramInfo hwInfo = new ProgramInfo();
+ hwInfo.selector = hwSel;
+ hwInfo.logicallyTunedTo = hwSel.primaryId;
+ hwInfo.physicallyTunedTo = hwSel.primaryId;
+ hwInfo.signalQuality = hwSignalQuality;
+ hwInfo.relatedContent = new ArrayList<>();
+ hwInfo.metadata = new ArrayList<>();
+ return hwInfo;
+ }
+
static VendorKeyValue makeVendorKeyValue(String vendorKey, String vendorValue) {
VendorKeyValue vendorKeyValue = new VendorKeyValue();
vendorKeyValue.key = vendorKey;
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
new file mode 100644
index 000000000000..9b62dc75419f
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2022 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.broadcastradio.hal2;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.hardware.broadcastradio.V2_0.Constants;
+import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
+import android.hardware.broadcastradio.V2_0.ITunerCallback;
+import android.hardware.broadcastradio.V2_0.ITunerSession;
+import android.hardware.broadcastradio.V2_0.IdentifierType;
+import android.hardware.broadcastradio.V2_0.ProgramInfo;
+import android.hardware.broadcastradio.V2_0.Result;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+import android.util.ArraySet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.verification.VerificationWithTimeout;
+
+import java.util.ArrayList;
+
+/**
+ * Tests for HIDL HAL TunerSession.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public final class TunerSessionHidlTest {
+
+ private static final VerificationWithTimeout CALLBACK_TIMEOUT =
+ timeout(/* millis= */ 200);
+ private static final int SIGNAL_QUALITY = 1;
+ private static final long AM_FM_FREQUENCY_SPACING = 500;
+ private static final long[] AM_FM_FREQUENCY_LIST = {97500, 98100, 99100};
+ private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR =
+ new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
+ /* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 100,
+ /* stereo= */ false, /* rds= */ false, /* ta= */ false, /* af= */ false,
+ /* ea= */ false);
+ private static final RadioManager.BandConfig FM_BAND_CONFIG =
+ new RadioManager.FmBandConfig(FM_BAND_DESCRIPTOR);
+ private static final int UNSUPPORTED_CONFIG_FLAG = 0;
+
+ private final Object mLock = new Object();
+ private RadioModule mRadioModule;
+ private ITunerCallback mHalTunerCallback;
+ private ProgramInfo mHalCurrentInfo;
+ private TunerSession[] mTunerSessions;
+
+ @Mock private IBroadcastRadio mBroadcastRadioMock;
+ @Mock ITunerSession mHalTunerSessionMock;
+ private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks;
+
+ @Before
+ public void setup() throws Exception {
+ mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(
+ /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
+ /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
+ /* numAudioSources= */ 0, /* isInitializationRequired= */ false,
+ /* isCaptureSupported= */ false, /* bands= */ null, /* isBgScanSupported= */ false,
+ new int[] {}, new int[] {},
+ /* dabFrequencyTable= */ null, /* vendorInfo= */ null), mLock);
+
+ doAnswer(invocation -> {
+ mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
+ IBroadcastRadio.openSessionCallback cb = (IBroadcastRadio.openSessionCallback)
+ invocation.getArguments()[1];
+ cb.onValues(Result.OK, mHalTunerSessionMock);
+ return null;
+ }).when(mBroadcastRadioMock).openSession(any(), any());
+
+ doAnswer(invocation -> {
+ android.hardware.broadcastradio.V2_0.ProgramSelector halSel =
+ (android.hardware.broadcastradio.V2_0.ProgramSelector)
+ invocation.getArguments()[0];
+ mHalCurrentInfo = TestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
+ if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY) {
+ return Result.NOT_SUPPORTED;
+ }
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return Result.OK;
+ }).when(mHalTunerSessionMock).tune(any());
+
+ doAnswer(invocation -> {
+ if ((boolean) invocation.getArguments()[0]) {
+ mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
+ } else {
+ mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
+ }
+ mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return Result.OK;
+ }).when(mHalTunerSessionMock).step(anyBoolean());
+
+ doAnswer(invocation -> {
+ if (mHalCurrentInfo == null) {
+ android.hardware.broadcastradio.V2_0.ProgramSelector placeHolderSelector =
+ TestUtils.makeHalFmSelector(/* freq= */ 97300);
+
+ mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
+ return Result.OK;
+ }
+ mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
+ mHalCurrentInfo.selector.primaryId.value,
+ !(boolean) invocation.getArguments()[0]);
+ mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return Result.OK;
+ }).when(mHalTunerSessionMock).scan(anyBoolean(), anyBoolean());
+
+ when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(new ArrayList<Byte>(0));
+ }
+
+ @Test
+ public void openSession_withMultipleSessions() throws Exception {
+ int numSessions = 3;
+
+ openAidlClients(numSessions);
+
+ for (int index = 0; index < numSessions; index++) {
+ assertWithMessage("Session of index %s close state", index)
+ .that(mTunerSessions[index].isClosed()).isFalse();
+ }
+ }
+
+ @Test
+ public void setConfiguration() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+
+ mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onConfigurationChanged(FM_BAND_CONFIG);
+ }
+
+ @Test
+ public void getConfiguration() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
+
+ RadioManager.BandConfig config = mTunerSessions[0].getConfiguration();
+
+ assertWithMessage("Session configuration").that(config)
+ .isEqualTo(FM_BAND_CONFIG);
+ }
+
+ @Test
+ public void setMuted_withUnmuted() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+
+ mTunerSessions[0].setMuted(/* mute= */ false);
+
+ assertWithMessage("Session mute state after setting unmuted")
+ .that(mTunerSessions[0].isMuted()).isFalse();
+ }
+
+ @Test
+ public void setMuted_withMuted() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+
+ mTunerSessions[0].setMuted(/* mute= */ true);
+
+ assertWithMessage("Session mute state after setting muted")
+ .that(mTunerSessions[0].isMuted()).isTrue();
+ }
+
+ @Test
+ public void close_withOneSession() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+
+ mTunerSessions[0].close();
+
+ assertWithMessage("Close state of broadcast radio service session")
+ .that(mTunerSessions[0].isClosed()).isTrue();
+ }
+
+ @Test
+ public void close_withOnlyOneSession_withMultipleSessions() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+ int closeIdx = 0;
+
+ mTunerSessions[closeIdx].close();
+
+ for (int index = 0; index < numSessions; index++) {
+ if (index == closeIdx) {
+ assertWithMessage(
+ "Close state of broadcast radio service session of index %s", index)
+ .that(mTunerSessions[index].isClosed()).isTrue();
+ } else {
+ assertWithMessage(
+ "Close state of broadcast radio service session of index %s", index)
+ .that(mTunerSessions[index].isClosed()).isFalse();
+ }
+ }
+ }
+
+ @Test
+ public void close_withOneSession_withError() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int errorCode = RadioTuner.ERROR_SERVER_DIED;
+
+ mTunerSessions[0].close(errorCode);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onError(errorCode);
+ assertWithMessage("Close state of broadcast radio service session")
+ .that(mTunerSessions[0].isClosed()).isTrue();
+ }
+
+ @Test
+ public void closeSessions_withMultipleSessions_withError() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+
+ int errorCode = RadioTuner.ERROR_SERVER_DIED;
+ mRadioModule.closeSessions(errorCode);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT).onError(errorCode);
+ assertWithMessage("Close state of broadcast radio service session of index %s", index)
+ .that(mTunerSessions[index].isClosed()).isTrue();
+ }
+ }
+
+ @Test
+ public void tune_withOneSession() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+ RadioManager.ProgramInfo tuneInfo =
+ TestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
+
+ mTunerSessions[0].tune(initialSel);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo);
+ }
+
+ @Test
+ public void tune_withMultipleSessions() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+ ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+ RadioManager.ProgramInfo tuneInfo =
+ TestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
+
+ mTunerSessions[0].tune(initialSel);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(tuneInfo);
+ }
+ }
+
+ @Test
+ public void tune_withUnsupportedSelector_throwsException() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramSelector unsupportedSelector = TestUtils.makeProgramSelector(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, /* value= */ 300));
+
+ UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+ () -> mTunerSessions[0].tune(unsupportedSelector));
+
+ assertWithMessage("Exception for tuning on unsupported program selector")
+ .that(thrown).hasMessageThat().contains("tune: NOT_SUPPORTED");
+ }
+
+ @Test
+ public void step_withDirectionUp() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[1];
+ ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
+ RadioManager.ProgramInfo stepUpInfo = TestUtils.makeProgramInfo(
+ TestUtils.makeFmSelector(initFreq + AM_FM_FREQUENCY_SPACING), SIGNAL_QUALITY);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = TestUtils.makeHalProgramInfo(
+ Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
+
+ mTunerSessions[0].step(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(stepUpInfo);
+ }
+
+ @Test
+ public void step_withDirectionDown() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[1];
+ ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
+ RadioManager.ProgramInfo stepDownInfo = TestUtils.makeProgramInfo(
+ TestUtils.makeFmSelector(initFreq - AM_FM_FREQUENCY_SPACING),
+ SIGNAL_QUALITY);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = TestUtils.makeHalProgramInfo(
+ Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
+
+ mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(stepDownInfo);
+ }
+
+ @Test
+ public void scan_withDirectionUp() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[2];
+ ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
+ RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo(
+ TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
+ SIGNAL_QUALITY);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = TestUtils.makeHalProgramInfo(
+ Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
+
+ mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(scanUpInfo);
+ }
+
+ @Test
+ public void scan_callsOnTuneFailedWhenTimeout() throws Exception {
+ int numSessions = 2;
+ openAidlClients(numSessions);
+
+ mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+ .onTuneFailed(eq(Result.TIMEOUT), any());
+ }
+ }
+
+ @Test
+ public void scan_withDirectionDown() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[2];
+ ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
+ RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo(
+ TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
+ SIGNAL_QUALITY);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = TestUtils.makeHalProgramInfo(
+ Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
+
+ mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(scanUpInfo);
+ }
+
+ @Test
+ public void cancel() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+ mTunerSessions[0].tune(initialSel);
+
+ mTunerSessions[0].cancel();
+
+ verify(mHalTunerSessionMock).cancel();
+ }
+
+ @Test
+ public void getImage_withInvalidId_throwsIllegalArgumentException() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int imageId = Constants.INVALID_IMAGE;
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ mTunerSessions[0].getImage(imageId);
+ });
+
+ assertWithMessage("Exception for getting image with invalid ID")
+ .that(thrown).hasMessageThat().contains("Image ID is missing");
+ }
+
+ @Test
+ public void getImage_withValidId() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ int imageId = 1;
+
+ Bitmap imageTest = mTunerSessions[0].getImage(imageId);
+
+ assertWithMessage("Null image").that(imageTest).isEqualTo(null);
+ }
+
+ @Test
+ public void startBackgroundScan() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+
+ mTunerSessions[0].startBackgroundScan();
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onBackgroundScanComplete();
+ }
+
+ @Test
+ public void stopProgramListUpdates() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false);
+ mTunerSessions[0].startProgramListUpdates(aidlFilter);
+
+ mTunerSessions[0].stopProgramListUpdates();
+
+ verify(mHalTunerSessionMock).stopProgramListUpdates();
+ }
+
+ private void openAidlClients(int numClients) throws Exception {
+ mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients];
+ mTunerSessions = new TunerSession[numClients];
+ for (int index = 0; index < numClients; index++) {
+ mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
+ mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
+ }
+ }
+
+ private long getSeekFrequency(long currentFrequency, boolean seekDown) {
+ long seekFrequency;
+ if (seekDown) {
+ seekFrequency = AM_FM_FREQUENCY_LIST[AM_FM_FREQUENCY_LIST.length - 1];
+ for (int i = AM_FM_FREQUENCY_LIST.length - 1; i >= 0; i--) {
+ if (AM_FM_FREQUENCY_LIST[i] < currentFrequency) {
+ seekFrequency = AM_FM_FREQUENCY_LIST[i];
+ break;
+ }
+ }
+ } else {
+ seekFrequency = AM_FM_FREQUENCY_LIST[0];
+ for (int index = 0; index < AM_FM_FREQUENCY_LIST.length; index++) {
+ if (AM_FM_FREQUENCY_LIST[index] > currentFrequency) {
+ seekFrequency = AM_FM_FREQUENCY_LIST[index];
+ break;
+ }
+ }
+ }
+ return seekFrequency;
+ }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index b292d7dfafe2..a0ed0266d24b 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -294,10 +294,9 @@ public class TransactionParcelTests {
StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */);
- IApplicationThread appThread = new StubAppThread();
Binder activityToken = new Binder();
- ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken);
+ ClientTransaction transaction = ClientTransaction.obtain(null, activityToken);
transaction.addCallback(callback1);
transaction.addCallback(callback2);
transaction.setLifecycleStateRequest(lifecycleRequest);
@@ -318,10 +317,9 @@ public class TransactionParcelTests {
ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
config());
- IApplicationThread appThread = new StubAppThread();
Binder activityToken = new Binder();
- ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken);
+ ClientTransaction transaction = ClientTransaction.obtain(null, activityToken);
transaction.addCallback(callback1);
transaction.addCallback(callback2);
@@ -339,10 +337,9 @@ public class TransactionParcelTests {
// Write to parcel
StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */);
- IApplicationThread appThread = new StubAppThread();
Binder activityToken = new Binder();
- ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken);
+ ClientTransaction transaction = ClientTransaction.obtain(null, activityToken);
transaction.setLifecycleStateRequest(lifecycleRequest);
writeAndPrepareForReading(transaction);
@@ -400,286 +397,4 @@ public class TransactionParcelTests {
}
};
}
-
- /** Stub implementation of IApplicationThread that can be presented as {@link Binder}. */
- class StubAppThread extends android.app.IApplicationThread.Stub {
-
- @Override
- public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
- }
-
- @Override
- public void scheduleReceiver(Intent intent, ActivityInfo activityInfo,
- CompatibilityInfo compatibilityInfo, int i, String s, Bundle bundle, boolean b,
- int i1, int i2) throws RemoteException {
- }
-
- @Override
- public void scheduleCreateService(IBinder iBinder, ServiceInfo serviceInfo,
- CompatibilityInfo compatibilityInfo, int i) throws RemoteException {
- }
-
- @Override
- public void scheduleStopService(IBinder iBinder) throws RemoteException {
- }
-
- @Override
- public void bindApplication(String s, ApplicationInfo applicationInfo,
- String sdkSandboxClientAppVolumeUuid, String sdkSandboxClientAppPackage,
- ProviderInfoList list, ComponentName componentName, ProfilerInfo profilerInfo,
- Bundle bundle, IInstrumentationWatcher iInstrumentationWatcher,
- IUiAutomationConnection iUiAutomationConnection, int i, boolean b, boolean b1,
- boolean b2, boolean b3, Configuration configuration,
- CompatibilityInfo compatibilityInfo, Map map, Bundle bundle1, String s1,
- AutofillOptions ao, ContentCaptureOptions co, long[] disableCompatChanges,
- SharedMemory serializedSystemFontMap,
- long startRequestedElapsedTime, long startRequestedUptime)
- throws RemoteException {
- }
-
- @Override
- public void scheduleExit() throws RemoteException {
- }
-
- @Override
- public void scheduleServiceArgs(IBinder iBinder, ParceledListSlice parceledListSlice)
- throws RemoteException {
- }
-
- @Override
- public void updateTimeZone() throws RemoteException {
- }
-
- @Override
- public void processInBackground() throws RemoteException {
- }
-
- @Override
- public void scheduleBindService(IBinder iBinder, Intent intent, boolean b, int i)
- throws RemoteException {
- }
-
- @Override
- public void scheduleUnbindService(IBinder iBinder, Intent intent) throws RemoteException {
- }
-
- @Override
- public void dumpService(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
- String[] strings) throws RemoteException {
- }
-
- @Override
- public void scheduleRegisteredReceiver(IIntentReceiver iIntentReceiver, Intent intent,
- int i, String s, Bundle bundle, boolean b, boolean b1, int i1, int i2)
- throws RemoteException {
- }
-
- @Override
- public void scheduleLowMemory() throws RemoteException {
- }
-
- @Override
- public void profilerControl(boolean b, ProfilerInfo profilerInfo, int i)
- throws RemoteException {
- }
-
- @Override
- public void setSchedulingGroup(int i) throws RemoteException {
- }
-
- @Override
- public void scheduleCreateBackupAgent(ApplicationInfo applicationInfo,
- int i, int userId, int operatioType)
- throws RemoteException {
- }
-
- @Override
- public void scheduleDestroyBackupAgent(ApplicationInfo applicationInfo,
- int userId) throws RemoteException {
- }
-
- @Override
- public void scheduleOnNewActivityOptions(IBinder iBinder, Bundle bundle)
- throws RemoteException {
- }
-
- @Override
- public void scheduleSuicide() throws RemoteException {
- }
-
- @Override
- public void dispatchPackageBroadcast(int i, String[] strings) throws RemoteException {
- }
-
- @Override
- public void scheduleCrash(String s, int i, Bundle extras) throws RemoteException {
- }
-
- @Override
- public void dumpActivity(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
- String s, String[] strings) throws RemoteException {
- }
-
- @Override
- public void clearDnsCache() throws RemoteException {
- }
-
- @Override
- public void updateHttpProxy() throws RemoteException {
- }
-
- @Override
- public void setCoreSettings(Bundle bundle) throws RemoteException {
- }
-
- @Override
- public void updatePackageCompatibilityInfo(String s, CompatibilityInfo compatibilityInfo)
- throws RemoteException {
- }
-
- @Override
- public void scheduleTrimMemory(int i) throws RemoteException {
- }
-
- @Override
- public void dumpMemInfo(ParcelFileDescriptor parcelFileDescriptor,
- Debug.MemoryInfo memoryInfo, boolean b, boolean b1, boolean b2, boolean b3,
- boolean b4, String[] strings) throws RemoteException {
- }
-
- @Override
- public void dumpMemInfoProto(ParcelFileDescriptor parcelFileDescriptor,
- Debug.MemoryInfo memoryInfo, boolean b, boolean b1, boolean b2,
- boolean b3, String[] strings) throws RemoteException {
- }
-
- @Override
- public void dumpGfxInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
- throws RemoteException {
- }
-
- @Override
- public void dumpCacheInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
- throws RemoteException {
- }
-
- @Override
- public void dumpProvider(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
- String[] strings) throws RemoteException {
- }
-
- @Override
- public void dumpDbInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
- throws RemoteException {
- }
-
- @Override
- public void unstableProviderDied(IBinder iBinder) throws RemoteException {
- }
-
- @Override
- public void requestAssistContextExtras(IBinder iBinder, IBinder iBinder1, int i, int i1,
- int i2) throws RemoteException {
- }
-
- @Override
- public void scheduleTranslucentConversionComplete(IBinder iBinder, boolean b)
- throws RemoteException {
- }
-
- @Override
- public void setProcessState(int i) throws RemoteException {
- }
-
- @Override
- public void scheduleInstallProvider(ProviderInfo providerInfo) throws RemoteException {
- }
-
- @Override
- public void updateTimePrefs(int i) throws RemoteException {
- }
-
- @Override
- public void scheduleEnterAnimationComplete(IBinder iBinder) throws RemoteException {
- }
-
- @Override
- public void notifyCleartextNetwork(byte[] bytes) throws RemoteException {
- }
-
- @Override
- public void startBinderTracking() throws RemoteException {
- }
-
- @Override
- public void stopBinderTrackingAndDump(ParcelFileDescriptor parcelFileDescriptor)
- throws RemoteException {
- }
-
- @Override
- public void scheduleLocalVoiceInteractionStarted(IBinder iBinder,
- IVoiceInteractor iVoiceInteractor) throws RemoteException {
- }
-
- @Override
- public void handleTrustStorageUpdate() throws RemoteException {
- }
-
- @Override
- public void attachAgent(String s) throws RemoteException {
- }
-
- @Override
- public void attachStartupAgents(String s) throws RemoteException {
- }
-
- @Override
- public void scheduleApplicationInfoChanged(ApplicationInfo applicationInfo)
- throws RemoteException {
- }
-
- @Override
- public void setNetworkBlockSeq(long l) throws RemoteException {
- }
-
- @Override
- public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String path,
- ParcelFileDescriptor fd, RemoteCallback finishCallback) {
- }
-
- @Override
- public void dumpResources(ParcelFileDescriptor fd, RemoteCallback finishCallback) {
- }
-
- @Override
- public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
- }
-
- @Override
- public void requestDirectActions(IBinder activityToken, IVoiceInteractor interactor,
- RemoteCallback cancellationCallback, RemoteCallback resultCallback) {
- }
-
- @Override
- public void performDirectAction(IBinder activityToken, String actionId, Bundle arguments,
- RemoteCallback cancellationCallback, RemoteCallback resultCallback) {
- }
-
- @Override
- public void notifyContentProviderPublishStatus(ContentProviderHolder holder, String auth,
- int userId, boolean published) {
- }
-
- @Override
- public void instrumentWithoutRestart(ComponentName instrumentationName,
- Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
- IUiAutomationConnection instrumentationUiConnection, ApplicationInfo targetInfo) {
- }
-
- @Override
- public void updateUiTranslationState(IBinder activityToken, int state,
- TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
- UiTranslationSpec uiTranslationSpec) {
- }
- }
}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index c1b4dcc1d3ff..4cc06e33ab62 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2179,12 +2179,6 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/WindowContainer.java"
},
- "-23020844": {
- "message": "Back: Reset surfaces",
- "level": "DEBUG",
- "group": "WM_DEBUG_BACK_PREVIEW",
- "at": "com\/android\/server\/wm\/BackNavigationController.java"
- },
"-21399771": {
"message": "activity %s already destroying, skipping request with reason:%s",
"level": "VERBOSE",
@@ -3823,12 +3817,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "1544805551": {
- "message": "Skipping app transition animation. task=%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_BACK_PREVIEW",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"1557732761": {
"message": "For Intent %s bringing to top: %s",
"level": "DEBUG",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 64220c82fd9a..b6327e5d91f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -62,7 +62,6 @@ import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -82,8 +81,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
SETTING_VALUE_OFF) == SETTING_VALUE_ON;
/** Predictive back animation developer option */
private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
- // TODO (b/241808055) Find a appropriate time to remove during refactor
- private static final boolean ENABLE_SHELL_TRANSITIONS = Transitions.ENABLE_SHELL_TRANSITIONS;
/**
* Max duration to wait for a transition to finish before accepting another gesture start
* request.
@@ -121,8 +118,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private final TouchTracker mTouchTracker = new TouchTracker();
private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
- private final Transitions mTransitions;
- private BackTransitionHandler mBackTransitionHandler;
@VisibleForTesting
final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
@@ -148,11 +143,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
- Context context,
- Transitions transitions) {
+ Context context) {
this(shellInit, shellController, shellExecutor, backgroundHandler,
- ActivityTaskManager.getService(), context, context.getContentResolver(),
- transitions);
+ ActivityTaskManager.getService(), context, context.getContentResolver());
}
@VisibleForTesting
@@ -162,8 +155,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull IActivityTaskManager activityTaskManager,
- Context context, ContentResolver contentResolver,
- Transitions transitions) {
+ Context context, ContentResolver contentResolver) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
@@ -171,7 +163,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mContentResolver = contentResolver;
mBgHandler = bgHandler;
shellInit.addInitCallback(this::onInit, this);
- mTransitions = transitions;
}
@VisibleForTesting
@@ -182,10 +173,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private void onInit() {
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
createAdapter();
- if (ENABLE_SHELL_TRANSITIONS) {
- mBackTransitionHandler = new BackTransitionHandler(this);
- mTransitions.addHandler(mBackTransitionHandler);
- }
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
this::createExternalInterface, this);
@@ -335,17 +322,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
- // In legacy transition, it would use `Task.mBackGestureStarted` in core to handle the
- // following transition when back callback is invoked.
- // If the back callback is not invoked, we should reset the token and finish the whole back
- // navigation without waiting the transition.
- if (!ENABLE_SHELL_TRANSITIONS) {
- finishBackNavigation();
- } else if (!mTriggerBack) {
- // reset the token to prevent it consume next transition.
- mBackTransitionHandler.setDepartingWindowContainerToken(null);
- finishBackNavigation();
- }
+ finishBackNavigation();
}
/**
@@ -614,10 +591,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
mTransitionInProgress = true;
- if (ENABLE_SHELL_TRANSITIONS) {
- mBackTransitionHandler.setDepartingWindowContainerToken(
- mBackNavigationInfo.getDepartingWindowContainerToken());
- }
mShellExecutor.executeDelayed(mResetTransitionRunnable, MAX_TRANSITION_DURATION);
}
@@ -626,19 +599,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mTransitionInProgress = false;
}
- /**
- * This should be called from {@link BackTransitionHandler#startAnimation} when the following
- * transition is triggered by the real back callback in {@link #onBackAnimationFinished}.
- * Will consume the default transition and finish current back navigation.
- */
- void finishTransition(Transitions.TransitionFinishCallback finishCallback) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishTransition()");
- mShellExecutor.execute(() -> {
- finishBackNavigation();
- finishCallback.onTransitionFinished(null, null);
- });
- }
-
private void createAdapter() {
IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java
deleted file mode 100644
index 6d72d9c1f637..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2022 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.wm.shell.back;
-
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.wm.shell.transition.Transitions;
-
-class BackTransitionHandler implements Transitions.TransitionHandler {
- private BackAnimationController mBackAnimationController;
- private WindowContainerToken mDepartingWindowContainerToken;
-
- BackTransitionHandler(@NonNull BackAnimationController backAnimationController) {
- mBackAnimationController = backAnimationController;
- }
-
- @Override
- public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (mDepartingWindowContainerToken != null) {
- final TransitionInfo.Change change = info.getChange(mDepartingWindowContainerToken);
- if (change == null) {
- return false;
- }
-
- startTransaction.hide(change.getLeash());
- startTransaction.apply();
- mDepartingWindowContainerToken = null;
- mBackAnimationController.finishTransition(finishCallback);
- return true;
- }
-
- return false;
- }
-
- @Nullable
- @Override
- public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
- @NonNull TransitionRequestInfo request) {
- return null;
- }
-
- @Override
- public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- }
-
- void setDepartingWindowContainerToken(
- @Nullable WindowContainerToken departingWindowContainerToken) {
- mDepartingWindowContainerToken = departingWindowContainerToken;
- }
-}
-
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 1977dcb81e97..962be9da2111 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -260,13 +260,12 @@ public abstract class WMShellBaseModule {
ShellInit shellInit,
ShellController shellController,
@ShellMainThread ShellExecutor shellExecutor,
- @ShellBackgroundThread Handler backgroundHandler,
- Transitions transitions
+ @ShellBackgroundThread Handler backgroundHandler
) {
if (BackAnimationController.IS_ENABLED) {
return Optional.of(
new BackAnimationController(shellInit, shellController, shellExecutor,
- backgroundHandler, context, transitions));
+ backgroundHandler, context));
}
return Optional.empty();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 928e71f8d3a6..63d4a6f5acd9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -41,6 +41,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
@@ -395,6 +396,11 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
}
+ // The back gesture has animated this change before transition happen, so here we don't
+ // play the animation again.
+ if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+ continue;
+ }
// Don't animate anything that isn't independent.
if (!TransitionInfo.isIndependent(change, info)) continue;
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
index ead451f07653..4a3284e1953b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
@@ -41,6 +41,7 @@ import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import org.junit.Assert.assertNotNull
import java.util.Collections
internal object SplitScreenUtils {
@@ -50,7 +51,7 @@ internal object SplitScreenUtils {
private const val DIVIDER_BAR = "docked_divider_handle"
private const val OVERVIEW_SNAPSHOT = "snapshot"
private const val GESTURE_STEP_MS = 16L
- private const val LONG_PRESS_TIME_MS = 100L
+ private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L
private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
private val notificationScrollerSelector: BySelector
@@ -275,13 +276,6 @@ internal object SplitScreenUtils {
}
}
- fun longPress(instrumentation: Instrumentation, point: Point) {
- val downTime = SystemClock.uptimeMillis()
- touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
- SystemClock.sleep(LONG_PRESS_TIME_MS)
- touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
- }
-
fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) {
tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
val allApps = tapl.workspace.switchToAllApps()
@@ -353,9 +347,11 @@ internal object SplitScreenUtils {
Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")),
TIMEOUT_MS
)
- longPress(instrumentation, textView.visibleCenter)
+ assertNotNull("Unable to find the TextView", textView)
+ textView.click(LONG_PRESS_TIME_MS)
val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+ assertNotNull("Unable to find the copy button", copyBtn)
copyBtn.click()
// Paste text to destinationApp
@@ -364,9 +360,11 @@ internal object SplitScreenUtils {
Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
TIMEOUT_MS
)
- longPress(instrumentation, editText.visibleCenter)
+ assertNotNull("Unable to find the EditText", editText)
+ editText.click(LONG_PRESS_TIME_MS)
val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+ assertNotNull("Unable to find the paste button", pasteBtn)
pasteBtn.click()
// Verify text
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 7896247c5f5a..7eccbf42e67d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -64,7 +64,6 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellSharedConstants;
-import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Rule;
@@ -103,9 +102,6 @@ public class BackAnimationControllerTest extends ShellTestCase {
private IRemoteAnimationRunner mBackAnimationRunner;
@Mock
- private Transitions mTransitions;
-
- @Mock
private ShellController mShellController;
private BackAnimationController mController;
@@ -127,7 +123,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
mController = new BackAnimationController(mShellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
- mContentResolver, mTransitions);
+ mContentResolver);
mController.setEnableUAnimation(true);
mShellInit.init();
mEventTime = 0;
@@ -225,7 +221,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
mController = new BackAnimationController(shellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
- mContentResolver, mTransitions);
+ mContentResolver);
shellInit.init();
mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index b4b908dbff16..161ea255dfb4 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -603,7 +603,7 @@ public final class MediaRouter2 {
*/
public void transferTo(@NonNull MediaRoute2Info route) {
if (isSystemRouter()) {
- sManager.selectRoute(mClientPackageName, route);
+ sManager.transfer(mClientPackageName, route);
return;
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index b6f07f43cef5..e403e246f318 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -448,14 +448,16 @@ public final class MediaRouter2Manager {
}
/**
- * Selects media route for the specified package name.
+ * Transfers a {@link RoutingSessionInfo routing session} belonging to a specified package name
+ * to a {@link MediaRoute2Info media route}.
+ *
+ * <p>Same as {@link #transfer(RoutingSessionInfo, MediaRoute2Info)}, but resolves the routing
+ * session based on the provided package name.
*/
- public void selectRoute(@NonNull String packageName, @NonNull MediaRoute2Info route) {
+ public void transfer(@NonNull String packageName, @NonNull MediaRoute2Info route) {
Objects.requireNonNull(packageName, "packageName must not be null");
Objects.requireNonNull(route, "route must not be null");
- Log.v(TAG, "Selecting route. packageName= " + packageName + ", route=" + route);
-
List<RoutingSessionInfo> sessionInfos = getRoutingSessions(packageName);
RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
transfer(targetSession, route);
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 810b408370c7..4193ffad5a0a 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -384,7 +384,7 @@ public class MediaRouter2ManagerTest {
MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
assertThat(routeToSelect).isNotNull();
- mManager.selectRoute(mPackageName, routeToSelect);
+ mManager.transfer(mPackageName, routeToSelect);
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(mManager.getRemoteSessions()).hasSize(1);
}
@@ -410,7 +410,7 @@ public class MediaRouter2ManagerTest {
assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(1);
- mManager.selectRoute(mPackageName, routeToSelect);
+ mManager.transfer(mPackageName, routeToSelect);
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
@@ -514,7 +514,7 @@ public class MediaRouter2ManagerTest {
}
});
awaitOnRouteChangedManager(
- () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)),
+ () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID1)),
ROUTE_ID1,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
@@ -525,7 +525,7 @@ public class MediaRouter2ManagerTest {
RoutingSessionInfo sessionInfo = sessions.get(1);
awaitOnRouteChangedManager(
- () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)),
+ () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)),
ROUTE_ID5_TO_TRANSFER_TO,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
@@ -583,9 +583,9 @@ public class MediaRouter2ManagerTest {
assertThat(route1).isNotNull();
assertThat(route2).isNotNull();
- mManager.selectRoute(mPackageName, route1);
+ mManager.transfer(mPackageName, route1);
assertThat(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
- mManager.selectRoute(mPackageName, route2);
+ mManager.transfer(mPackageName, route2);
assertThat(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
// onTransferFailed/onSessionReleased should not be called.
@@ -703,7 +703,7 @@ public class MediaRouter2ManagerTest {
}
});
- mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1));
+ mManager.transfer(mPackageName, routes.get(ROUTE_ID1));
assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
@@ -858,7 +858,7 @@ public class MediaRouter2ManagerTest {
});
mRouter2.setOnGetControllerHintsListener(listener);
- mManager.selectRoute(mPackageName, route);
+ mManager.transfer(mPackageName, route);
assertThat(hintLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
@@ -903,7 +903,7 @@ public class MediaRouter2ManagerTest {
}
});
- mManager.selectRoute(mPackageName, routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+ mManager.transfer(mPackageName, routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
}
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 25fa34b418ab..cdd1a8964b2b 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -21,14 +21,14 @@
<string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for all your sign-ins?</string>
<string name="set_as_default">Set as default</string>
<string name="use_once">Use once</string>
- <string name="choose_create_option_description">You can use saved <xliff:g id="type">%1$s</xliff:g> on any device. It will be saved to <xliff:g id="providerInfoDisplayName">%2$s</xliff:g> for <xliff:g id="createInfoDisplayName">%3$s</xliff:g></string>
+ <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName">%1$s</xliff:g> <xliff:g id="type">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName">%4$s</xliff:g></string>
<string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords, <xliff:g id="passkeysNumber">%2$s</xliff:g> passkeys</string>
<string name="more_options_usage_passwords"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords</string>
<string name="more_options_usage_passkeys"><xliff:g id="passkeysNumber">%1$s</xliff:g> passkeys</string>
- <string name="passkeys">passkeys</string>
- <string name="passwords">passwords</string>
+ <string name="passkey">passkey</string>
+ <string name="password">password</string>
<string name="sign_ins">sign-ins</string>
- <string name="createOptionInfo_icon_description">CreateOptionInfo credentialType icon</string>
+ <string name="other_password_manager">Other password manager</string>
<!-- Spoken content description of an element which will close the sheet when clicked. -->
<string name="close_sheet">"Close sheet"</string>
<!-- Spoken content description of the back arrow button. -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 2099a235a3e8..b71af2b654e4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -28,6 +28,7 @@ import android.credentials.ui.Constants
import android.credentials.ui.Entry
import android.credentials.ui.CreateCredentialProviderData
import android.credentials.ui.GetCredentialProviderData
+import android.credentials.ui.DisabledProviderData
import android.credentials.ui.ProviderData
import android.credentials.ui.RequestInfo
import android.credentials.ui.BaseDialogResult
@@ -39,7 +40,7 @@ import android.os.ResultReceiver
import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreatePasskeyUiState
import com.android.credentialmanager.createflow.CreateScreenState
-import com.android.credentialmanager.createflow.ProviderInfo
+import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
@@ -51,7 +52,8 @@ class CredentialManagerRepo(
intent: Intent,
) {
private val requestInfo: RequestInfo
- private val providerList: List<ProviderData>
+ private val providerEnabledList: List<ProviderData>
+ private val providerDisabledList: List<DisabledProviderData>
// TODO: require non-null.
val resultReceiver: ResultReceiver?
@@ -61,16 +63,16 @@ class CredentialManagerRepo(
RequestInfo::class.java
) ?: testCreateRequestInfo()
- providerList = when (requestInfo.type) {
+ providerEnabledList = when (requestInfo.type) {
RequestInfo.TYPE_CREATE ->
intent.extras?.getParcelableArrayList(
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
CreateCredentialProviderData::class.java
- ) ?: testCreateCredentialProviderList()
+ ) ?: testCreateCredentialEnabledProviderList()
RequestInfo.TYPE_GET ->
intent.extras?.getParcelableArrayList(
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
- GetCredentialProviderData::class.java
+ DisabledProviderData::class.java
) ?: testGetCredentialProviderList()
else -> {
// TODO: fail gracefully
@@ -78,6 +80,12 @@ class CredentialManagerRepo(
}
}
+ providerDisabledList =
+ intent.extras?.getParcelableArrayList(
+ ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST,
+ DisabledProviderData::class.java
+ ) ?: testDisabledProviderList()
+
resultReceiver = intent.getParcelableExtra(
Constants.EXTRA_RESULT_RECEIVER,
ResultReceiver::class.java
@@ -103,25 +111,28 @@ class CredentialManagerRepo(
}
fun getCredentialInitialUiState(): GetCredentialUiState {
- val providerList = GetFlowUtils.toProviderList(
+ val providerEnabledList = GetFlowUtils.toProviderList(
// TODO: handle runtime cast error
- providerList as List<GetCredentialProviderData>, context)
+ providerEnabledList as List<GetCredentialProviderData>, context)
// TODO: covert from real requestInfo
val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo("tribank")
return GetCredentialUiState(
- providerList,
+ providerEnabledList,
GetScreenState.PRIMARY_SELECTION,
requestDisplayInfo,
)
}
fun createPasskeyInitialUiState(): CreatePasskeyUiState {
- val providerList = CreateFlowUtils.toProviderList(
+ val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
+ // Handle runtime cast error
+ providerEnabledList as List<CreateCredentialProviderData>, context)
+ val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
// Handle runtime cast error
- providerList as List<CreateCredentialProviderData>, context)
+ providerDisabledList as List<DisabledProviderData>, context)
var hasDefault = false
- var defaultProvider: ProviderInfo = providerList.first()
- providerList.forEach{providerInfo -> providerInfo.createOptions =
+ var defaultProvider: EnabledProviderInfo = providerEnabledList.first()
+ providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
// TODO: covert from real requestInfo
@@ -131,7 +142,8 @@ class CredentialManagerRepo(
TYPE_PUBLIC_KEY_CREDENTIAL,
"tribank")
return CreatePasskeyUiState(
- providers = providerList,
+ enabledProviders = providerEnabledList,
+ disabledProviders = providerDisabledList,
if (hasDefault)
{CreateScreenState.CREATION_OPTION_SELECTION} else {CreateScreenState.PASSKEY_INTRO},
requestDisplayInfo,
@@ -157,10 +169,10 @@ class CredentialManagerRepo(
}
// TODO: below are prototype functionalities. To be removed for productionization.
- private fun testCreateCredentialProviderList(): List<CreateCredentialProviderData> {
+ private fun testCreateCredentialEnabledProviderList(): List<CreateCredentialProviderData> {
return listOf(
CreateCredentialProviderData
- .Builder("com.google/com.google.CredentialManagerService")
+ .Builder("io.enpass.app")
.setSaveEntries(
listOf<Entry>(
newCreateEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
@@ -172,7 +184,7 @@ class CredentialManagerRepo(
.setIsDefaultProvider(true)
.build(),
CreateCredentialProviderData
- .Builder("com.dashlane/com.dashlane.CredentialManagerService")
+ .Builder("com.dashlane")
.setSaveEntries(
listOf<Entry>(
newCreateEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
@@ -185,6 +197,13 @@ class CredentialManagerRepo(
)
}
+ private fun testDisabledProviderList(): List<DisabledProviderData> {
+ return listOf(
+ DisabledProviderData("com.lastpass.lpandroid"),
+ DisabledProviderData("com.google.android.youtube")
+ )
+ }
+
private fun testGetCredentialProviderList(): List<GetCredentialProviderData> {
return listOf(
GetCredentialProviderData.Builder("com.google/com.google.CredentialManagerService")
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 5c7956423469..53e817e1a03a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -17,9 +17,11 @@
package com.android.credentialmanager
import android.content.Context
+import android.content.pm.PackageManager
import android.credentials.ui.Entry
import android.credentials.ui.GetCredentialProviderData
import android.credentials.ui.CreateCredentialProviderData
+import android.credentials.ui.DisabledProviderData
import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.getflow.ActionEntryInfo
import com.android.credentialmanager.getflow.AuthenticationEntryInfo
@@ -103,22 +105,42 @@ class GetFlowUtils {
class CreateFlowUtils {
companion object {
- fun toProviderList(
+ fun toEnabledProviderList(
providerDataList: List<CreateCredentialProviderData>,
context: Context,
- ): List<com.android.credentialmanager.createflow.ProviderInfo> {
+ ): List<com.android.credentialmanager.createflow.EnabledProviderInfo> {
+ val packageManager = context.packageManager
return providerDataList.map {
- com.android.credentialmanager.createflow.ProviderInfo(
- // TODO: replace to extract from the service data structure when available
- icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ val pkgInfo = packageManager
+ .getPackageInfo(it.providerFlattenedComponentName,
+ PackageManager.PackageInfoFlags.of(0))
+ com.android.credentialmanager.createflow.EnabledProviderInfo(
+ icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
name = it.providerFlattenedComponentName,
- displayName = it.providerFlattenedComponentName,
+ displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
createOptions = toCreationOptionInfoList(it.saveEntries, context),
isDefault = it.isDefaultProvider,
)
}
}
+ fun toDisabledProviderList(
+ providerDataList: List<DisabledProviderData>,
+ context: Context,
+ ): List<com.android.credentialmanager.createflow.DisabledProviderInfo> {
+ val packageManager = context.packageManager
+ return providerDataList.map {
+ val pkgInfo = packageManager
+ .getPackageInfo(it.providerFlattenedComponentName,
+ PackageManager.PackageInfoFlags.of(0))
+ com.android.credentialmanager.createflow.DisabledProviderInfo(
+ icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
+ name = it.providerFlattenedComponentName,
+ displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
+ )
+ }
+ }
+
private fun toCreationOptionInfoList(
creationEntries: List<Entry>,
context: Context,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 21190e7dc8c4..0c3447f02b14 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -18,13 +18,25 @@ package com.android.credentialmanager.createflow
import android.graphics.drawable.Drawable
-data class ProviderInfo(
+open class ProviderInfo(
val icon: Drawable,
val name: String,
val displayName: String,
+)
+
+class EnabledProviderInfo(
+ icon: Drawable,
+ name: String,
+ displayName: String,
var createOptions: List<CreateOptionInfo>,
val isDefault: Boolean,
-)
+) : ProviderInfo(icon, name, displayName)
+
+class DisabledProviderInfo(
+ icon: Drawable,
+ name: String,
+ displayName: String,
+) : ProviderInfo(icon, name, displayName)
open class EntryInfo (
val entryKey: String,
@@ -55,7 +67,7 @@ data class RequestDisplayInfo(
* user selects a different entry on the more option page.
*/
data class ActiveEntry (
- val activeProvider: ProviderInfo,
+ val activeProvider: EnabledProviderInfo,
val activeEntryInfo: EntryInfo,
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index 437e7b213620..4769a26b8eb1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -21,6 +21,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.Add
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
@@ -59,7 +60,7 @@ fun CreatePasskeyScreen(
onCancel = viewModel::onCancel,
)
CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
- providerList = uiState.providers,
+ enabledProviderList = uiState.enabledProviders,
onCancel = viewModel::onCancel,
onProviderSelected = viewModel::onProviderSelected
)
@@ -70,14 +71,16 @@ fun CreatePasskeyScreen(
onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected,
onConfirm = viewModel::onPrimaryCreateOptionInfoSelected,
onCancel = viewModel::onCancel,
- multiProvider = uiState.providers.size > 1,
+ multiProvider = uiState.enabledProviders.size > 1,
onMoreOptionsSelected = viewModel::onMoreOptionsSelected
)
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
- providerList = uiState.providers,
+ enabledProviderList = uiState.enabledProviders,
+ disabledProviderList = uiState.disabledProviders,
onBackButtonSelected = viewModel::onBackButtonSelected,
- onOptionSelected = viewModel::onMoreOptionsRowSelected
+ onOptionSelected = viewModel::onMoreOptionsRowSelected,
+ onDisabledPasswordManagerSelected = viewModel::onDisabledPasswordManagerSelected
)
CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
providerInfo = uiState.activeEntry?.activeProvider!!,
@@ -153,7 +156,7 @@ fun ConfirmationCard(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProviderSelectionCard(
- providerList: List<ProviderInfo>,
+ enabledProviderList: List<EnabledProviderInfo>,
onProviderSelected: (String) -> Unit,
onCancel: () -> Unit
) {
@@ -182,7 +185,7 @@ fun ProviderSelectionCard(
LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
- providerList.forEach {
+ enabledProviderList.forEach {
item {
ProviderRow(providerInfo = it, onProviderSelected = onProviderSelected)
}
@@ -212,9 +215,11 @@ fun ProviderSelectionCard(
@Composable
fun MoreOptionsSelectionCard(
requestDisplayInfo: RequestDisplayInfo,
- providerList: List<ProviderInfo>,
+ enabledProviderList: List<EnabledProviderInfo>,
+ disabledProviderList: List<DisabledProviderInfo>,
onBackButtonSelected: () -> Unit,
- onOptionSelected: (ActiveEntry) -> Unit
+ onOptionSelected: (ActiveEntry) -> Unit,
+ onDisabledPasswordManagerSelected: () -> Unit,
) {
Card() {
Column() {
@@ -250,18 +255,24 @@ fun MoreOptionsSelectionCard(
LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
- providerList.forEach { providerInfo ->
- providerInfo.createOptions.forEach { createOptionInfo ->
+ enabledProviderList.forEach { enabledProviderInfo ->
+ enabledProviderInfo.createOptions.forEach { createOptionInfo ->
item {
MoreOptionsInfoRow(
- providerInfo = providerInfo,
+ providerInfo = enabledProviderInfo,
createOptionInfo = createOptionInfo,
onOptionSelected = {
- onOptionSelected(ActiveEntry(providerInfo, createOptionInfo))
+ onOptionSelected(ActiveEntry(enabledProviderInfo, createOptionInfo))
})
}
}
}
+ item {
+ MoreOptionsDisabledProvidersRow(
+ disabledProviders = disabledProviderList,
+ onDisabledPasswordManagerSelected = onDisabledPasswordManagerSelected,
+ )
+ }
}
}
Divider(
@@ -276,7 +287,7 @@ fun MoreOptionsSelectionCard(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsRowIntroCard(
- providerInfo: ProviderInfo,
+ providerInfo: EnabledProviderInfo,
onDefaultOrNotSelected: () -> Unit,
) {
Card() {
@@ -347,7 +358,7 @@ fun CreationSelectionCard(
Card() {
Column() {
Icon(
- bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+ bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
contentDescription = null,
tint = Color.Unspecified,
modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
@@ -366,18 +377,14 @@ fun CreationSelectionCard(
.align(alignment = Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
)
- Text(
- text = requestDisplayInfo.appDomainName,
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
- )
if (createOptionInfo.userProviderDisplayName != null) {
Text(
text = stringResource(
R.string.choose_create_option_description,
+ requestDisplayInfo.appDomainName,
when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkeys)
- TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.passwords)
+ TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkey)
+ TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.password)
else -> stringResource(R.string.sign_ins)
},
providerInfo.displayName,
@@ -460,7 +467,7 @@ fun PrimaryCreateOptionRow(
icon = {
Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
- contentDescription = stringResource(R.string.createOptionInfo_icon_description))
+ contentDescription = null)
},
shape = MaterialTheme.shapes.large,
label = {
@@ -483,7 +490,7 @@ fun PrimaryCreateOptionRow(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsInfoRow(
- providerInfo: ProviderInfo,
+ providerInfo: EnabledProviderInfo,
createOptionInfo: CreateOptionInfo,
onOptionSelected: () -> Unit
) {
@@ -491,9 +498,9 @@ fun MoreOptionsInfoRow(
modifier = Modifier.fillMaxWidth(),
onClick = onOptionSelected,
icon = {
- Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
- bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
- contentDescription = stringResource(R.string.createOptionInfo_icon_description))
+ Image(modifier = Modifier.size(32.dp, 32.dp).padding(start = 10.dp),
+ bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+ contentDescription = null)
},
shape = MaterialTheme.shapes.large,
label = {
@@ -546,4 +553,37 @@ fun MoreOptionsInfoRow(
}
}
)
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MoreOptionsDisabledProvidersRow(
+ disabledProviders: List<ProviderInfo>,
+ onDisabledPasswordManagerSelected: () -> Unit,
+) {
+ SuggestionChip(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = onDisabledPasswordManagerSelected,
+ icon = {
+ Icon(
+ Icons.Filled.Add,
+ contentDescription = null
+ )
+ },
+ shape = MaterialTheme.shapes.large,
+ label = {
+ Column() {
+ Text(
+ text = stringResource(R.string.other_password_manager),
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ Text(
+ text = disabledProviders.joinToString(separator = ", "){ it.displayName },
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+ )
} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
index 2e9758aece33..8b94201ad787 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -28,7 +28,8 @@ import com.android.credentialmanager.common.DialogResult
import com.android.credentialmanager.common.ResultState
data class CreatePasskeyUiState(
- val providers: List<ProviderInfo>,
+ val enabledProviders: List<EnabledProviderInfo>,
+ val disabledProviders: List<DisabledProviderInfo>,
val currentScreenState: CreateScreenState,
val requestDisplayInfo: RequestDisplayInfo,
val activeEntry: ActiveEntry? = null,
@@ -50,15 +51,15 @@ class CreatePasskeyViewModel(
}
fun onConfirmIntro() {
- if (uiState.providers.size > 1) {
+ if (uiState.enabledProviders.size > 1) {
uiState = uiState.copy(
currentScreenState = CreateScreenState.PROVIDER_SELECTION
)
- } else if (uiState.providers.size == 1){
+ } else if (uiState.enabledProviders.size == 1){
uiState = uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
- activeEntry = ActiveEntry(uiState.providers.first(),
- uiState.providers.first().createOptions.first())
+ activeEntry = ActiveEntry(uiState.enabledProviders.first(),
+ uiState.enabledProviders.first().createOptions.first())
)
} else {
throw java.lang.IllegalStateException("Empty provider list.")
@@ -73,8 +74,8 @@ class CreatePasskeyViewModel(
)
}
- fun getProviderInfoByName(providerName: String): ProviderInfo {
- return uiState.providers.single {
+ fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
+ return uiState.enabledProviders.single {
it.name.equals(providerName)
}
}
@@ -98,6 +99,10 @@ class CreatePasskeyViewModel(
)
}
+ fun onDisabledPasswordManagerSelected() {
+ // TODO: Complete this function
+ }
+
fun onCancel() {
CredentialManagerRepo.getInstance().onCancel()
dialogResult.value = DialogResult(ResultState.CANCELED)
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index f1a24aff4319..50cab84e0d9d 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -35,8 +35,8 @@
</activity>
<provider
- android:name=".GalleryEntryProvider"
- android:authorities="com.android.spa.gallery.provider"
+ android:name="com.android.settingslib.spa.framework.SpaSearchProvider"
+ android:authorities="com.android.spa.gallery.search.provider"
android:enabled="true"
android:exported="false">
</provider>
@@ -51,7 +51,7 @@
</activity>
<provider
android:name="com.android.settingslib.spa.framework.debug.DebugProvider"
- android:authorities="com.android.spa.gallery.debug"
+ android:authorities="com.android.spa.gallery.debug.provider"
android:enabled="true"
android:exported="false">
</provider>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
deleted file mode 100644
index 817c209fbcdc..000000000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2022 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.settingslib.spa.gallery
-
-import com.android.settingslib.spa.framework.EntryProvider
-
-class GalleryEntryProvider : EntryProvider()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 742e2712e41a..016b27f0c82c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -80,7 +80,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
override val browseActivityClass = GalleryMainActivity::class.java
- override val entryProviderAuthorities = "com.android.spa.gallery.provider"
+ override val searchProviderAuthorities = "com.android.spa.gallery.search.provider"
override val logger = LocalLogger()
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
index 38f41bc44a89..35b9c0fbd16b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
@@ -33,7 +33,7 @@ import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.addUri
import com.android.settingslib.spa.framework.common.getColumns
-private const val TAG = "EntryProvider"
+private const val TAG = "SpaSearchProvider"
/**
* The content provider to return entry related data, which can be used for search and hierarchy.
@@ -47,7 +47,7 @@ private const val TAG = "EntryProvider"
* $ adb shell content query --uri content://<AuthorityPath>/search_mutable_status
* $ adb shell content query --uri content://<AuthorityPath>/search_immutable_status
*/
-open class EntryProvider : ContentProvider() {
+class SpaSearchProvider : ContentProvider() {
private val spaEnvironment get() = SpaEnvironmentFactory.instance
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index b83104360260..a9cb041cc198 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -64,7 +64,7 @@ abstract class SpaEnvironment(context: Context) {
open val browseActivityClass: Class<out Activity>? = null
- open val entryProviderAuthorities: String? = null
+ open val searchProviderAuthorities: String? = null
open val logger: SpaLogger = object : SpaLogger {}
diff --git a/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml
new file mode 100644
index 000000000000..46abff81d38f
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml
@@ -0,0 +1,33 @@
+<!--
+ Copyright (C) 2022 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M1.03 8.47l0.43-4.96h4.33v1.17H2.48L2.25 7.39C2.66 7.1 3.1 6.96 3.57 6.96c0.77 0 1.38 0.3 1.83 0.9 s0.66 1.41 0.66 2.43c0 1.03-0.24 1.84-0.72 2.43S4.2 13.6 3.36 13.6c-0.75 0-1.36-0.24-1.83-0.73s-0.74-1.16-0.81-2.02h1.13 c0.07 0.57 0.23 1 0.49 1.29s0.59 0.43 1.01 0.43c0.47 0 0.84-0.2 1.1-0.61c0.26-0.41 0.4-0.96 0.4-1.65 c0-0.65-0.14-1.18-0.43-1.59S3.76 8.09 3.28 8.09c-0.4 0-0.72 0.1-0.96 0.31L1.99 8.73L1.03 8.47z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M 18.93,5.74 L 18.93,3.39 L 17.63,3.39 L 17.63,5.74 L 15.28,5.74 L 15.28,7.04 L 17.63,7.04 L 17.63,9.39 L 18.93,9.39 L 18.93,7.04 L 21.28,7.04 L 21.28,5.74 z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.78 12.24l-0.22 0.27c-0.63 0.73-1.55 1.1-2.76 1.1c-1.08 0-1.92-0.36-2.53-1.07s-0.93-1.72-0.94-3.02V7.56 c0-1.39 0.28-2.44 0.84-3.13s1.39-1.04 2.51-1.04c0.95 0 1.69 0.26 2.23 0.79s0.83 1.28 0.89 2.26h-1.25 c-0.05-0.62-0.22-1.1-0.52-1.45s-0.74-0.52-1.34-0.52c-0.72 0-1.24 0.23-1.57 0.7S8.6 6.37 8.59 7.4v2.03c0 1 0.19 1.77 0.57 2.31 c0.38 0.54 0.93 0.8 1.65 0.8c0.67 0 1.19-0.16 1.54-0.49l0.18-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/values/carrierid_icon_overrides.xml b/packages/SettingsLib/res/values/carrierid_icon_overrides.xml
new file mode 100644
index 000000000000..d2ae52d8347a
--- /dev/null
+++ b/packages/SettingsLib/res/values/carrierid_icon_overrides.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<!--
+ ~ This resource file exists to enumerate all network type icon overrides on a
+ ~ per-carrierId basis
+-->
+<resources>
+ <!--
+ Network type (RAT) icon overrides can be configured here on a per-carrierId basis.
+ 1. Add a new TypedArray here, using the naming scheme below
+ 2. The entries are (NetworkType, drawable ID) pairs
+ 3. Add this array's ID to the MAPPING field of MobileIconCarrierIdOverrides.kt
+ -->
+ <array name="carrierId_2032_iconOverrides">
+ <item>5G_PLUS</item>
+ <item>@drawable/ic_5g_plus_mobiledata_default</item>
+ </array>
+</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 7913c16b6b98..65c94cec6009 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -36,8 +36,10 @@ import android.content.pm.PackageStats;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -1577,8 +1579,8 @@ public class ApplicationsState {
public long internalSize;
public long externalSize;
public String labelDescription;
-
public boolean mounted;
+ public boolean showInPersonalTab;
/**
* Setting this to {@code true} prevents the entry to be filtered by
@@ -1635,6 +1637,33 @@ public class ApplicationsState {
ThreadUtils.postOnBackgroundThread(
() -> this.ensureLabelDescriptionLocked(context));
}
+ this.showInPersonalTab = shouldShowInPersonalTab(context, info.uid);
+ }
+
+ /**
+ * Checks if the user that the app belongs to have the property
+ * {@link UserProperties#SHOW_IN_SETTINGS_WITH_PARENT} set.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ boolean shouldShowInPersonalTab(Context context, int uid) {
+ UserManager userManager = UserManager.get(context);
+ int userId = UserHandle.getUserId(uid);
+
+ // Regardless of apk version, if the app belongs to the current user then return true.
+ if (userId == ActivityManager.getCurrentUser()) {
+ return true;
+ }
+
+ // For sdk version < 34, if the app doesn't belong to the current user,
+ // then as per earlier behaviour the app shouldn't be displayed in personal tab.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ return false;
+ }
+
+ UserProperties userProperties = userManager.getUserProperties(
+ UserHandle.of(userId));
+ return userProperties.getShowInSettings()
+ == UserProperties.SHOW_IN_SETTINGS_WITH_PARENT;
}
public void ensureLabel(Context context) {
@@ -1784,7 +1813,7 @@ public class ApplicationsState {
@Override
public boolean filterApp(AppEntry entry) {
- return UserHandle.getUserId(entry.info.uid) == mCurrentUser;
+ return entry.showInPersonalTab;
}
};
@@ -1811,7 +1840,7 @@ public class ApplicationsState {
@Override
public boolean filterApp(AppEntry entry) {
- return UserHandle.getUserId(entry.info.uid) != mCurrentUser;
+ return !entry.showInPersonalTab;
}
};
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index c829bc316246..3ba51d2a2602 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -293,7 +293,7 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
return false;
}
setConnectedRecord();
- mRouterManager.selectRoute(mPackageName, mRouteInfo);
+ mRouterManager.transfer(mPackageName, mRouteInfo);
return true;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt
new file mode 100644
index 000000000000..a0395b559291
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2022 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.settingslib.mobile
+
+import android.annotation.DrawableRes
+import android.content.res.Resources
+import android.content.res.TypedArray
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.settingslib.R
+import com.android.settingslib.SignalIcon.MobileIconGroup
+
+/**
+ * This class defines a network type (3G, 4G, etc.) override mechanism on a per-carrierId basis.
+ *
+ * Traditionally, carrier-customized network type iconography was achieved using the `MCC/MNC`
+ * resource qualifiers, and swapping out the drawable resource by name. It would look like this:
+ *
+ * res/
+ * drawable/
+ * 3g_mobiledata_icon.xml
+ * drawable-MCC-MNC/
+ * 3g_mobiledata_icon.xml
+ *
+ * This would mean that, provided a context created with this MCC/MNC configuration set, loading
+ * the network type icon through [MobileIconGroup] would provide a carrier-defined network type
+ * icon rather than the AOSP-defined default.
+ *
+ * The MCC/MNC mechanism no longer can fully define carrier-specific network type icons, because
+ * there is no longer a 1:1 mapping between MCC/MNC and carrier. With the advent of MVNOs, multiple
+ * carriers can have the same MCC/MNC value, but wish to differentiate based on their carrier ID.
+ * CarrierId is a newer concept than MCC/MNC, and provides more granularity when it comes to
+ * determining the carrier (e.g. MVNOs can share MCC/MNC values with the network owner), therefore
+ * it can fit all of the same use cases currently handled by `MCC/MNC`, without the need to apply a
+ * configuration context in order to get the proper UI for a given SIM icon.
+ *
+ * NOTE: CarrierId icon overrides will always take precedence over those defined using `MCC/MNC`
+ * resource qualifiers.
+ *
+ * [MAPPING] encodes the relationship between CarrierId and the corresponding override array
+ * that exists in the config.xml. An alternative approach could be to generate the resource name
+ * by string concatenation at run-time:
+ *
+ * val resName = "carrierId_$carrierId_iconOverrides"
+ * val override = resources.getResourceIdentifier(resName)
+ *
+ * However, that's going to be far less efficient until MAPPING grows to a sufficient size. For now,
+ * given a relatively small number of entries, we should just maintain the mapping here.
+ */
+interface MobileIconCarrierIdOverrides {
+ @DrawableRes
+ fun getOverrideFor(carrierId: Int, networkType: String, resources: Resources): Int
+ fun carrierIdEntryExists(carrierId: Int): Boolean
+}
+
+class MobileIconCarrierIdOverridesImpl : MobileIconCarrierIdOverrides {
+ @DrawableRes
+ override fun getOverrideFor(carrierId: Int, networkType: String, resources: Resources): Int {
+ val resId = MAPPING[carrierId] ?: return 0
+ val ta = resources.obtainTypedArray(resId)
+ val map = parseNetworkIconOverrideTypedArray(ta)
+ ta.recycle()
+ return map[networkType] ?: 0
+ }
+
+ override fun carrierIdEntryExists(carrierId: Int) =
+ overrideExists(carrierId, MAPPING)
+
+ companion object {
+ private const val TAG = "MobileIconOverrides"
+ /**
+ * This map maintains the lookup from the canonical carrier ID (see below link) to the
+ * corresponding overlay resource. New overrides should add an entry below in order to
+ * change the network type icon resources based on carrier ID
+ *
+ * Refer to the link below for the canonical mapping maintained in AOSP:
+ * https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb
+ */
+ private val MAPPING = mapOf(
+ // 2032 == Xfinity Mobile
+ 2032 to R.array.carrierId_2032_iconOverrides,
+ )
+
+ /**
+ * Parse `carrierId_XXXX_iconOverrides` for a particular network type. The resource file
+ * "carrierid_icon_overrides.xml" defines a TypedArray format for overriding specific
+ * network type icons (a.k.a. RAT icons) for a particular carrier ID. The format is defined
+ * as an array of (network type name, drawable) pairs:
+ * <array name="carrierId_XXXX_iconOverrides>
+ * <item>NET_TYPE_1</item>
+ * <item>@drawable/net_type_1_override</item>
+ * <item>NET_TYPE_2</item>
+ * <item>@drawable/net_type_2_override</item>
+ * </array>
+ *
+ * @param ta the [TypedArray] defined in carrierid_icon_overrides.xml
+ * @return the overridden drawable resource ID if it exists, or 0 if it does not
+ */
+ @VisibleForTesting
+ @JvmStatic
+ fun parseNetworkIconOverrideTypedArray(ta: TypedArray): Map<String, Int> {
+ if (ta.length() % 2 != 0) {
+ Log.w(TAG,
+ "override must contain an even number of (key, value) entries. skipping")
+
+ return mapOf()
+ }
+
+ val result = mutableMapOf<String, Int>()
+ // The array is defined as Pair(String, resourceId), so walk by 2
+ for (i in 0 until ta.length() step 2) {
+ val key = ta.getString(i)
+ val override = ta.getResourceId(i + 1, 0)
+ if (key == null || override == 0) {
+ Log.w(TAG, "Invalid override found. Skipping")
+ continue
+ }
+ result[key] = override
+ }
+
+ return result
+ }
+
+ @JvmStatic
+ private fun overrideExists(carrierId: Int, mapping: Map<Int, Int>): Boolean =
+ mapping.containsKey(carrierId)
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index f1e1e7d920cc..c5598bfa9438 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -293,4 +293,15 @@ public class ApplicationsStateTest {
assertThat(ApplicationsState.FILTER_MOVIES.filterApp(mEntry)).isFalse();
}
+
+ @Test
+ public void testPersonalAndWorkFiltersDisplaysCorrectApps() {
+ mEntry.showInPersonalTab = true;
+ assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isTrue();
+ assertThat(ApplicationsState.FILTER_WORK.filterApp(mEntry)).isFalse();
+
+ mEntry.showInPersonalTab = false;
+ assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
+ assertThat(ApplicationsState.FILTER_WORK.filterApp(mEntry)).isTrue();
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index fc2bf0a9bd93..39875f7950e4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -17,6 +17,7 @@
package com.android.settingslib.applications;
import static android.os.UserHandle.MU_ENABLED;
+import static android.os.UserHandle.USER_SYSTEM;
import static com.google.common.truth.Truth.assertThat;
@@ -48,9 +49,11 @@ import android.content.pm.ModuleInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -58,6 +61,8 @@ import android.os.UserManager;
import android.text.TextUtils;
import android.util.IconDrawableFactory;
+import androidx.test.core.app.ApplicationProvider;
+
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.Callbacks;
import com.android.settingslib.applications.ApplicationsState.Session;
@@ -71,6 +76,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@@ -79,6 +85,7 @@ import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowContextImpl;
import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.Arrays;
@@ -95,6 +102,7 @@ public class ApplicationsStateRoboTest {
private final static String LAUNCHABLE_PACKAGE_NAME = "com.android.launchable";
private static final int PROFILE_USERID = 10;
+ private static final int PROFILE_USERID2 = 11;
private static final String PKG_1 = "PKG1";
private static final int OWNER_UID_1 = 1001;
@@ -106,6 +114,10 @@ public class ApplicationsStateRoboTest {
private static final String PKG_3 = "PKG3";
private static final int OWNER_UID_3 = 1003;
+ private static final int PROFILE_UID_3 = UserHandle.getUid(PROFILE_USERID2, OWNER_UID_3);
+
+ private static final String CLONE_USER = "clone_user";
+ private static final String RANDOM_USER = "random_user";
/** Class under test */
private ApplicationsState mApplicationsState;
@@ -113,6 +125,8 @@ public class ApplicationsStateRoboTest {
private Application mApplication;
+ @Spy
+ Context mContext = ApplicationProvider.getApplicationContext();
@Mock
private Callbacks mCallbacks;
@Captor
@@ -738,4 +752,51 @@ public class ApplicationsStateRoboTest {
when(configChanges.applyNewConfig(any(Resources.class))).thenReturn(false);
mApplicationsState.setInterestingConfigChanges(configChanges);
}
+
+ @Test
+ public void shouldShowInPersonalTab_forCurrentUser_returnsTrue() {
+ ApplicationInfo appInfo = createApplicationInfo(PKG_1);
+ AppEntry primaryUserApp = createAppEntry(appInfo, 1);
+
+ assertThat(primaryUserApp.shouldShowInPersonalTab(mContext, appInfo.uid)).isTrue();
+ }
+
+ @Test
+ public void shouldShowInPersonalTab_userProfilePreU_returnsFalse() {
+ ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
+ Build.VERSION_CODES.TIRAMISU);
+ // Create an app (and subsequent AppEntry) in a non-primary user profile.
+ ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1);
+ AppEntry nonPrimaryUserApp = createAppEntry(appInfo1, 1);
+
+ assertThat(nonPrimaryUserApp.shouldShowInPersonalTab(mContext, appInfo1.uid)).isFalse();
+ }
+
+ @Test
+ public void shouldShowInPersonalTab_currentUserIsParent_returnsAsPerUserPropertyOfProfile1() {
+ // Mark system user as parent for both profile users.
+ ShadowUserManager shadowUserManager = Shadow
+ .extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
+ shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID,
+ CLONE_USER, 0);
+ shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID2,
+ RANDOM_USER, 0);
+ shadowUserManager.setupUserProperty(PROFILE_USERID,
+ /*showInSettings*/ UserProperties.SHOW_IN_SETTINGS_WITH_PARENT);
+ shadowUserManager.setupUserProperty(PROFILE_USERID2,
+ /*showInSettings*/ UserProperties.SHOW_IN_SETTINGS_NO);
+
+ ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
+ Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+
+ // Treat PROFILE_USERID as a clone user profile and create an app PKG_1 in it.
+ ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1);
+ // Treat PROFILE_USERID2 as a random non-primary profile and create an app PKG_3 in it.
+ ApplicationInfo appInfo2 = createApplicationInfo(PKG_3, PROFILE_UID_3);
+ AppEntry nonPrimaryUserApp1 = createAppEntry(appInfo1, 1);
+ AppEntry nonPrimaryUserApp2 = createAppEntry(appInfo2, 2);
+
+ assertThat(nonPrimaryUserApp1.shouldShowInPersonalTab(mContext, appInfo1.uid)).isTrue();
+ assertThat(nonPrimaryUserApp2.shouldShowInPersonalTab(mContext, appInfo2.uid)).isFalse();
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index 5399f8a0eff0..c058a61a3e9e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -461,7 +461,7 @@ public class MediaDeviceTest {
public void connect_shouldSelectRoute() {
mInfoMediaDevice1.connect();
- verify(mMediaRouter2Manager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo1);
+ verify(mMediaRouter2Manager).transfer(TEST_PACKAGE_NAME, mRouteInfo1);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java
new file mode 100644
index 000000000000..740261d3bac2
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 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.settingslib.mobile;
+
+import static com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl.parseNetworkIconOverrideTypedArray;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.TypedArray;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Map;
+
+@RunWith(RobolectricTestRunner.class)
+public final class MobileIconCarrierIdOverridesTest {
+ private static final String OVERRIDE_ICON_1_NAME = "name_1";
+ private static final int OVERRIDE_ICON_1_RES = 1;
+
+ private static final String OVERRIDE_ICON_2_NAME = "name_2";
+ private static final int OVERRIDE_ICON_2_RES = 2;
+
+ NetworkOverrideTypedArrayMock mResourceMock;
+
+ @Before
+ public void setUp() {
+ mResourceMock = new NetworkOverrideTypedArrayMock(
+ new String[] { OVERRIDE_ICON_1_NAME, OVERRIDE_ICON_2_NAME },
+ new int[] { OVERRIDE_ICON_1_RES, OVERRIDE_ICON_2_RES }
+ );
+ }
+
+ @Test
+ public void testParse_singleOverride() {
+ mResourceMock.setOverrides(
+ new String[] { OVERRIDE_ICON_1_NAME },
+ new int[] { OVERRIDE_ICON_1_RES }
+ );
+
+ Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock());
+
+ assertThat(parsed.get(OVERRIDE_ICON_1_NAME)).isEqualTo(OVERRIDE_ICON_1_RES);
+ }
+
+ @Test
+ public void testParse_multipleOverrides() {
+ mResourceMock.setOverrides(
+ new String[] { OVERRIDE_ICON_1_NAME, OVERRIDE_ICON_2_NAME },
+ new int[] { OVERRIDE_ICON_1_RES, OVERRIDE_ICON_2_RES }
+ );
+
+ Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock());
+
+ assertThat(parsed.get(OVERRIDE_ICON_2_NAME)).isEqualTo(OVERRIDE_ICON_2_RES);
+ assertThat(parsed.get(OVERRIDE_ICON_1_NAME)).isEqualTo(OVERRIDE_ICON_1_RES);
+ }
+
+ @Test
+ public void testParse_nonexistentKey_isNull() {
+ mResourceMock.setOverrides(
+ new String[] { OVERRIDE_ICON_1_NAME },
+ new int[] { OVERRIDE_ICON_1_RES }
+ );
+
+ Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock());
+
+ assertThat(parsed.get(OVERRIDE_ICON_2_NAME)).isNull();
+ }
+
+ static class NetworkOverrideTypedArrayMock {
+ private Object[] mInterleaved;
+
+ private final TypedArray mMockTypedArray = mock(TypedArray.class);
+
+ NetworkOverrideTypedArrayMock(
+ String[] networkTypes,
+ int[] iconOverrides) {
+
+ mInterleaved = interleaveTypes(networkTypes, iconOverrides);
+
+ doAnswer(invocation -> {
+ return mInterleaved[(int) invocation.getArgument(0)];
+ }).when(mMockTypedArray).getString(/* index */ anyInt());
+
+ doAnswer(invocation -> {
+ return mInterleaved[(int) invocation.getArgument(0)];
+ }).when(mMockTypedArray).getResourceId(/* index */ anyInt(), /* default */ anyInt());
+
+ when(mMockTypedArray.length()).thenAnswer(invocation -> {
+ return mInterleaved.length;
+ });
+ }
+
+ TypedArray getMock() {
+ return mMockTypedArray;
+ }
+
+ void setOverrides(String[] types, int[] resIds) {
+ mInterleaved = interleaveTypes(types, resIds);
+ }
+
+ private Object[] interleaveTypes(String[] strs, int[] ints) {
+ assertThat(strs.length).isEqualTo(ints.length);
+
+ Object[] ret = new Object[strs.length * 2];
+
+ // Keep track of where we are in the interleaved array, but iterate the overrides
+ int interleavedIndex = 0;
+ for (int i = 0; i < strs.length; i++) {
+ ret[interleavedIndex] = strs[i];
+ interleavedIndex += 1;
+ ret[interleavedIndex] = ints[i];
+ interleavedIndex += 1;
+ }
+ return ret;
+ }
+ }
+}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 11237dca7804..47771aa3c774 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -195,9 +195,6 @@
<permission android:name="com.android.systemui.permission.FLAGS"
android:protectionLevel="signature" />
- <permission android:name="android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES"
- android:protectionLevel="signature|privileged" />
-
<!-- Adding Quick Settings tiles -->
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
@@ -996,12 +993,5 @@
android:excludeFromRecents="true"
android:exported="false">
</activity>
-
- <provider
- android:authorities="com.android.systemui.keyguard.quickaffordance"
- android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
- android:exported="true"
- android:permission="android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES"
- />
</application>
</manifest>
diff --git a/packages/SystemUI/res-keyguard/values-land/dimens.xml b/packages/SystemUI/res-keyguard/values-land/dimens.xml
index a4e7a5f12db4..f1aa54412b3b 100644
--- a/packages/SystemUI/res-keyguard/values-land/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-land/dimens.xml
@@ -27,4 +27,6 @@
<integer name="scaled_password_text_size">26</integer>
<dimen name="bouncer_user_switcher_y_trans">@dimen/status_bar_height</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 0a55cf779683..3861d983b309 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -124,6 +124,8 @@
<dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen>
<dimen name="bouncer_user_switcher_header_padding_end">44dp</dimen>
<dimen name="bouncer_user_switcher_y_trans">0dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
<!-- 2 * the margin + size should equal the plus_margin -->
<dimen name="user_switcher_icon_large_margin">16dp</dimen>
diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml
index a93691434bc0..d6c9e98d8b4d 100644
--- a/packages/SystemUI/res/layout/screen_record_options.xml
+++ b/packages/SystemUI/res/layout/screen_record_options.xml
@@ -50,6 +50,7 @@
android:importantForAccessibility="yes"/>
</LinearLayout>
<LinearLayout
+ android:id="@+id/show_taps"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index d9df3373bef1..707bc9e535ff 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -17,7 +17,6 @@
<resources>
<dimen name="notification_panel_margin_horizontal">48dp</dimen>
<dimen name="status_view_margin_horizontal">62dp</dimen>
- <dimen name="bouncer_user_switcher_y_trans">20dp</dimen>
<!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two
pages is margin * 2, and that makes tiles page not appear immediately after user swipes to
diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
index 97ead01669a9..b98165fb08f0 100644
--- a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
@@ -21,4 +21,6 @@
<!-- Space between status view and notification shelf -->
<dimen name="keyguard_status_view_bottom_margin">70dp</dimen>
<dimen name="keyguard_clock_top_margin">80dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">186dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">110dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index 17f82b50d7be..8b41a44b9ba3 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -21,7 +21,6 @@
for different hardware and product builds. -->
<resources>
<dimen name="status_view_margin_horizontal">124dp</dimen>
- <dimen name="bouncer_user_switcher_y_trans">200dp</dimen>
<dimen name="large_screen_shade_header_left_padding">24dp</dimen>
<dimen name="qqs_layout_padding_bottom">40dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0e48760b7e27..6824d7f17e13 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -405,7 +405,7 @@
<string name="keyguard_face_failed">Can\u2019t recognize face</string>
<!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] -->
<string name="keyguard_suggest_fingerprint">Use fingerprint instead</string>
- <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=25] -->
+ <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=65] -->
<string name="keyguard_face_unlock_unavailable">Face unlock unavailable.</string>
<!-- Content description of the bluetooth icon when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt
deleted file mode 100644
index 8612b3a2c587..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2022 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.shared.keyguard.data.content
-
-import android.annotation.SuppressLint
-import android.content.ContentValues
-import android.content.Context
-import android.database.ContentObserver
-import android.graphics.drawable.Drawable
-import android.net.Uri
-import android.os.UserHandle
-import androidx.annotation.DrawableRes
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.withContext
-
-/** Collection of utility functions for using a content provider implementing the [Contract]. */
-object KeyguardQuickAffordanceProviderClient {
-
- /**
- * Selects an affordance with the given ID for a slot on the lock screen with the given ID.
- *
- * Note that the maximum number of selected affordances on this slot is automatically enforced.
- * Selecting a slot that is already full (e.g. already has a number of selected affordances at
- * its maximum capacity) will automatically remove the oldest selected affordance before adding
- * the one passed in this call. Additionally, selecting an affordance that's already one of the
- * selected affordances on the slot will move the selected affordance to the newest location in
- * the slot.
- */
- suspend fun insertSelection(
- context: Context,
- slotId: String,
- affordanceId: String,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ) {
- withContext(dispatcher) {
- context.contentResolver.insert(
- Contract.SelectionTable.URI,
- ContentValues().apply {
- put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
- put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
- }
- )
- }
- }
-
- /** Returns all available slots supported by the device. */
- suspend fun querySlots(
- context: Context,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ): List<Slot> {
- return withContext(dispatcher) {
- context.contentResolver
- .query(
- Contract.SlotTable.URI,
- null,
- null,
- null,
- null,
- )
- ?.use { cursor ->
- buildList {
- val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
- val capacityColumnIndex =
- cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
- if (idColumnIndex == -1 || capacityColumnIndex == -1) {
- return@buildList
- }
-
- while (cursor.moveToNext()) {
- add(
- Slot(
- id = cursor.getString(idColumnIndex),
- capacity = cursor.getInt(capacityColumnIndex),
- )
- )
- }
- }
- }
- }
- ?: emptyList()
- }
-
- /**
- * Returns [Flow] for observing the collection of slots.
- *
- * @see [querySlots]
- */
- fun observeSlots(
- context: Context,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ): Flow<List<Slot>> {
- return observeUri(
- context,
- Contract.SlotTable.URI,
- )
- .map { querySlots(context, dispatcher) }
- }
-
- /**
- * Returns all available affordances supported by the device, regardless of current slot
- * placement.
- */
- suspend fun queryAffordances(
- context: Context,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ): List<Affordance> {
- return withContext(dispatcher) {
- context.contentResolver
- .query(
- Contract.AffordanceTable.URI,
- null,
- null,
- null,
- null,
- )
- ?.use { cursor ->
- buildList {
- val idColumnIndex =
- cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
- val nameColumnIndex =
- cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
- val iconColumnIndex =
- cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
- if (idColumnIndex == -1 || nameColumnIndex == -1 || iconColumnIndex == -1) {
- return@buildList
- }
-
- while (cursor.moveToNext()) {
- add(
- Affordance(
- id = cursor.getString(idColumnIndex),
- name = cursor.getString(nameColumnIndex),
- iconResourceId = cursor.getInt(iconColumnIndex),
- )
- )
- }
- }
- }
- }
- ?: emptyList()
- }
-
- /**
- * Returns [Flow] for observing the collection of affordances.
- *
- * @see [queryAffordances]
- */
- fun observeAffordances(
- context: Context,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ): Flow<List<Affordance>> {
- return observeUri(
- context,
- Contract.AffordanceTable.URI,
- )
- .map { queryAffordances(context, dispatcher) }
- }
-
- /** Returns the current slot-affordance selections. */
- suspend fun querySelections(
- context: Context,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ): List<Selection> {
- return withContext(dispatcher) {
- context.contentResolver
- .query(
- Contract.SelectionTable.URI,
- null,
- null,
- null,
- null,
- )
- ?.use { cursor ->
- buildList {
- val slotIdColumnIndex =
- cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
- val affordanceIdColumnIndex =
- cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
- if (slotIdColumnIndex == -1 || affordanceIdColumnIndex == -1) {
- return@buildList
- }
-
- while (cursor.moveToNext()) {
- add(
- Selection(
- slotId = cursor.getString(slotIdColumnIndex),
- affordanceId = cursor.getString(affordanceIdColumnIndex),
- )
- )
- }
- }
- }
- }
- ?: emptyList()
- }
-
- /**
- * Returns [Flow] for observing the collection of selections.
- *
- * @see [querySelections]
- */
- fun observeSelections(
- context: Context,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ): Flow<List<Selection>> {
- return observeUri(
- context,
- Contract.SelectionTable.URI,
- )
- .map { querySelections(context, dispatcher) }
- }
-
- /** Unselects an affordance with the given ID from the slot with the given ID. */
- suspend fun deleteSelection(
- context: Context,
- slotId: String,
- affordanceId: String,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ) {
- withContext(dispatcher) {
- context.contentResolver.delete(
- Contract.SelectionTable.URI,
- "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
- " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
- arrayOf(
- slotId,
- affordanceId,
- ),
- )
- }
- }
-
- /** Unselects all affordances from the slot with the given ID. */
- suspend fun deleteAllSelections(
- context: Context,
- slotId: String,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ) {
- withContext(dispatcher) {
- context.contentResolver.delete(
- Contract.SelectionTable.URI,
- "${Contract.SelectionTable.Columns.SLOT_ID}",
- arrayOf(
- slotId,
- ),
- )
- }
- }
-
- private fun observeUri(
- context: Context,
- uri: Uri,
- ): Flow<Unit> {
- return callbackFlow {
- val observer =
- object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- trySend(Unit)
- }
- }
-
- context.contentResolver.registerContentObserver(
- uri,
- /* notifyForDescendants= */ true,
- observer,
- UserHandle.USER_CURRENT,
- )
-
- awaitClose { context.contentResolver.unregisterContentObserver(observer) }
- }
- .onStart { emit(Unit) }
- }
-
- @SuppressLint("UseCompatLoadingForDrawables")
- suspend fun getAffordanceIcon(
- context: Context,
- @DrawableRes iconResourceId: Int,
- dispatcher: CoroutineDispatcher = Dispatchers.IO,
- ): Drawable {
- return withContext(dispatcher) {
- context.packageManager
- .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
- .getDrawable(iconResourceId)
- }
- }
-
- data class Slot(
- val id: String,
- val capacity: Int,
- )
-
- data class Affordance(
- val id: String,
- val name: String,
- val iconResourceId: Int,
- )
-
- data class Selection(
- val slotId: String,
- val affordanceId: String,
- )
-
- private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
deleted file mode 100644
index c2658a9e61b1..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2022 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.shared.keyguard.data.content
-
-import android.content.ContentResolver
-import android.net.Uri
-
-/** Contract definitions for querying content about keyguard quick affordances. */
-object KeyguardQuickAffordanceProviderContract {
-
- const val AUTHORITY = "com.android.systemui.keyguard.quickaffordance"
- const val PERMISSION = "android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES"
-
- private val BASE_URI: Uri =
- Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build()
-
- /**
- * Table for slots.
- *
- * Slots are positions where affordances can be placed on the lock screen. Affordances that are
- * placed on slots are said to be "selected". The system supports the idea of multiple
- * affordances per slot, though the implementation may limit the number of affordances on each
- * slot.
- *
- * Supported operations:
- * - Query - to know which slots are available, query the [SlotTable.URI] [Uri]. The result set
- * will contain rows with the [SlotTable.Columns] columns.
- */
- object SlotTable {
- const val TABLE_NAME = "slots"
- val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
-
- object Columns {
- /** String. Unique ID for this slot. */
- const val ID = "id"
- /** Integer. The maximum number of affordances that can be placed in the slot. */
- const val CAPACITY = "capacity"
- }
- }
-
- /**
- * Table for affordances.
- *
- * Affordances are actions/buttons that the user can execute. They are placed on slots on the
- * lock screen.
- *
- * Supported operations:
- * - Query - to know about all the affordances that are available on the device, regardless of
- * which ones are currently selected, query the [AffordanceTable.URI] [Uri]. The result set will
- * contain rows, each with the columns specified in [AffordanceTable.Columns].
- */
- object AffordanceTable {
- const val TABLE_NAME = "affordances"
- val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
-
- object Columns {
- /** String. Unique ID for this affordance. */
- const val ID = "id"
- /** String. User-visible name for this affordance. */
- const val NAME = "name"
- /**
- * Integer. Resource ID for the drawable to load for this affordance. This is a resource
- * ID from the system UI package.
- */
- const val ICON = "icon"
- }
- }
-
- /**
- * Table for selections.
- *
- * Selections are pairs of slot and affordance IDs.
- *
- * Supported operations:
- * - Insert - to insert an affordance and place it in a slot, insert values for the columns into
- * the [SelectionTable.URI] [Uri]. The maximum capacity rule is enforced by the system.
- * Selecting a new affordance for a slot that is already full will automatically remove the
- * oldest affordance from the slot.
- * - Query - to know which affordances are set on which slots, query the [SelectionTable.URI]
- * [Uri]. The result set will contain rows, each of which with the columns from
- * [SelectionTable.Columns].
- * - Delete - to unselect an affordance, removing it from a slot, delete from the
- * [SelectionTable.URI] [Uri], passing in values for each column.
- */
- object SelectionTable {
- const val TABLE_NAME = "selections"
- val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
-
- object Columns {
- /** String. Unique ID for the slot. */
- const val SLOT_ID = "slot_id"
- /** String. Unique ID for the selected affordance. */
- const val AFFORDANCE_ID = "affordance_id"
- }
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index 40c8774d4f34..a790d89ac1ae 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -61,26 +61,42 @@ public class PreviewPositionHelper {
* Updates the matrix based on the provided parameters
*/
public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
- int canvasWidth, int canvasHeight, int screenWidthPx, int taskbarSize, boolean isTablet,
+ int canvasWidth, int canvasHeight, int screenWidthPx, int screenHeightPx,
+ int taskbarSize, boolean isTablet,
int currentRotation, boolean isRtl) {
boolean isRotated = false;
boolean isOrientationDifferent;
- float fullscreenTaskWidth = screenWidthPx;
- if (mSplitBounds != null && !mSplitBounds.appsStackedVertically) {
- // For landscape, scale the width
- float taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
- ? mSplitBounds.leftTaskPercent
- : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
- // Scale landscape width to that of actual screen
- fullscreenTaskWidth = screenWidthPx * taskPercent;
- }
int thumbnailRotation = thumbnailData.rotation;
int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
RectF thumbnailClipHint = new RectF();
- float canvasScreenRatio = canvasWidth / fullscreenTaskWidth;
- float scaledTaskbarSize = taskbarSize * canvasScreenRatio;
- thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
+
+ float scaledTaskbarSize = 0;
+ if (mSplitBounds != null) {
+ float fullscreenTaskWidth;
+ float fullscreenTaskHeight;
+ float canvasScreenRatio;
+
+ float taskPercent;
+ if (!mSplitBounds.appsStackedVertically) {
+ // For landscape, scale the width
+ taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
+ ? mSplitBounds.leftTaskPercent
+ : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
+ // Scale landscape width to that of actual screen
+ fullscreenTaskWidth = screenWidthPx * taskPercent;
+ canvasScreenRatio = canvasWidth / fullscreenTaskWidth;
+ } else {
+ taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT
+ ? mSplitBounds.leftTaskPercent
+ : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
+ // Scale landscape width to that of actual screen
+ fullscreenTaskHeight = screenHeightPx * taskPercent;
+ canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
+ }
+ scaledTaskbarSize = taskbarSize * canvasScreenRatio;
+ thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
+ }
float scale = thumbnailData.scale;
final float thumbnailScale;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 73229c321079..faaba63938bf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
@@ -152,7 +153,9 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
}
public void startAppearAnimation() {
- mMessageAreaController.setMessage(getInitialMessageResId());
+ if (TextUtils.isEmpty(mMessageAreaController.getMessage())) {
+ mMessageAreaController.setMessage(getInitialMessageResId());
+ }
mView.startAppearAnimation();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index 2bd3ca59b740..db986e0a631a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -103,6 +103,11 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
mView.setNextMessageColor(colorState);
}
+ /** Returns the message of the underlying TextView. */
+ public CharSequence getMessage() {
+ return mView.getText();
+ }
+
/**
* Reload colors from resources.
**/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 2bb3a5f437f5..ffcf42f74a55 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -1097,11 +1097,19 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
new KeyguardSecurityViewTransition());
}
int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans);
+ int viewFlipperBottomMargin = mResources.getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_view_mode_view_flipper_bottom_margin);
+ int userSwitcherBottomMargin = mResources.getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_view_mode_user_switcher_bottom_margin);
if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP, yTrans);
- constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
- constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
+ constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, mViewFlipper.getId(),
+ TOP, userSwitcherBottomMargin);
+ constraintSet.connect(mViewFlipper.getId(), TOP, mUserSwitcherViewGroup.getId(),
+ BOTTOM);
+ constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM,
+ viewFlipperBottomMargin);
constraintSet.centerHorizontally(mViewFlipper.getId(), PARENT_ID);
constraintSet.centerHorizontally(mUserSwitcherViewGroup.getId(), PARENT_ID);
constraintSet.setVerticalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD);
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index 5d52056d8b17..90ecb466b5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -169,7 +169,7 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView {
return
}
cutoutPath.reset()
- display.getDisplayInfo(displayInfo)
+ context.display?.getDisplayInfo(displayInfo)
displayInfo.displayCutout?.cutoutPath?.let { path -> cutoutPath.set(path) }
invalidate()
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 11d579d481c1..45f9385a2620 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -170,6 +170,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
private Display.Mode mDisplayMode;
@VisibleForTesting
protected DisplayInfo mDisplayInfo = new DisplayInfo();
+ private DisplayCutout mDisplayCutout;
@VisibleForTesting
protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
@@ -384,6 +385,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
mRotation = mDisplayInfo.rotation;
mDisplayMode = mDisplayInfo.getMode();
mDisplayUniqueId = mDisplayInfo.uniqueId;
+ mDisplayCutout = mDisplayInfo.displayCutout;
mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(),
mDisplayUniqueId);
mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
@@ -1022,7 +1024,8 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
mRoundedCornerResDelegate.dump(pw, args);
}
- private void updateConfiguration() {
+ @VisibleForTesting
+ void updateConfiguration() {
Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(),
"must call on " + mHandler.getLooper().getThread()
+ ", but was " + Thread.currentThread());
@@ -1033,11 +1036,14 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
mDotViewController.setNewRotation(newRotation);
}
final Display.Mode newMod = mDisplayInfo.getMode();
+ final DisplayCutout newCutout = mDisplayInfo.displayCutout;
if (!mPendingConfigChange
- && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) {
+ && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod)
+ || !Objects.equals(newCutout, mDisplayCutout))) {
mRotation = newRotation;
mDisplayMode = newMod;
+ mDisplayCutout = newCutout;
mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
getPhysicalPixelDisplaySizeRatio());
if (mScreenDecorHwcLayer != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index 588ef5c4e68f..4dfcd6398a4d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -16,16 +16,120 @@
package com.android.systemui.controls
+import android.Manifest
+import android.content.ComponentName
import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE
+import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+import android.content.pm.ResolveInfo
import android.content.pm.ServiceInfo
+import android.os.UserHandle
+import android.service.controls.ControlsProviderService
+import androidx.annotation.WorkerThread
import com.android.settingslib.applications.DefaultAppInfo
+import java.util.Objects
class ControlsServiceInfo(
- context: Context,
+ private val context: Context,
val serviceInfo: ServiceInfo
) : DefaultAppInfo(
context,
context.packageManager,
context.userId,
serviceInfo.componentName
-) \ No newline at end of file
+) {
+ private val _panelActivity: ComponentName?
+
+ init {
+ val metadata = serviceInfo.metaData
+ ?.getString(ControlsProviderService.META_DATA_PANEL_ACTIVITY) ?: ""
+ val unflatenned = ComponentName.unflattenFromString(metadata)
+ if (unflatenned != null && unflatenned.packageName == componentName.packageName) {
+ _panelActivity = unflatenned
+ } else {
+ _panelActivity = null
+ }
+ }
+
+ /**
+ * Component name of an activity that will be shown embedded in the device controls space
+ * instead of using the controls rendered by SystemUI.
+ *
+ * The activity must be in the same package, exported, enabled and protected by the
+ * [Manifest.permission.BIND_CONTROLS] permission.
+ */
+ var panelActivity: ComponentName? = null
+ private set
+
+ private var resolved: Boolean = false
+
+ @WorkerThread
+ fun resolvePanelActivity() {
+ if (resolved) return
+ resolved = true
+ panelActivity = _panelActivity?.let {
+ val resolveInfos = mPm.queryIntentActivitiesAsUser(
+ Intent().setComponent(it),
+ PackageManager.ResolveInfoFlags.of(
+ MATCH_DIRECT_BOOT_AWARE.toLong() or
+ MATCH_DIRECT_BOOT_UNAWARE.toLong()
+ ),
+ UserHandle.of(userId)
+ )
+ if (resolveInfos.isNotEmpty() && verifyResolveInfo(resolveInfos[0])) {
+ it
+ } else {
+ null
+ }
+ }
+ }
+
+ /**
+ * Verifies that the panel activity is enabled, exported and protected by the correct
+ * permission. This last check is to prevent apps from forgetting to protect the activity, as
+ * they won't be able to see the panel until they do.
+ */
+ @WorkerThread
+ private fun verifyResolveInfo(resolveInfo: ResolveInfo): Boolean {
+ return resolveInfo.activityInfo?.let {
+ it.permission == Manifest.permission.BIND_CONTROLS &&
+ it.exported && isComponentActuallyEnabled(it)
+ } ?: false
+ }
+
+ @WorkerThread
+ private fun isComponentActuallyEnabled(activityInfo: ActivityInfo): Boolean {
+ return when (mPm.getComponentEnabledSetting(activityInfo.componentName)) {
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> false
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> activityInfo.enabled
+ else -> false
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is ControlsServiceInfo &&
+ userId == other.userId &&
+ componentName == other.componentName &&
+ panelActivity == other.panelActivity
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(userId, componentName, panelActivity)
+ }
+
+ fun copy(): ControlsServiceInfo {
+ return ControlsServiceInfo(context, serviceInfo).also {
+ it.panelActivity = this.panelActivity
+ }
+ }
+
+ override fun toString(): String {
+ return """
+ ControlsServiceInfo(serviceInfo=$serviceInfo, panelActivity=$panelActivity, resolved=$resolved)
+ """.trimIndent()
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 2d76ff2774d6..115edd115ffe 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -18,17 +18,23 @@ package com.android.systemui.controls.management
import android.content.ComponentName
import android.content.Context
-import android.content.pm.ServiceInfo
import android.os.UserHandle
import android.service.controls.ControlsProviderService
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.applications.ServiceListing
import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.Dumpable
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.indentIfPossible
+import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
@@ -57,16 +63,19 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
private val context: Context,
@Background private val backgroundExecutor: Executor,
private val serviceListingBuilder: (Context) -> ServiceListing,
- userTracker: UserTracker
-) : ControlsListingController {
+ private val userTracker: UserTracker,
+ dumpManager: DumpManager,
+ featureFlags: FeatureFlags
+) : ControlsListingController, Dumpable {
@Inject
- constructor(context: Context, executor: Executor, userTracker: UserTracker): this(
- context,
- executor,
- ::createServiceListing,
- userTracker
- )
+ constructor(
+ context: Context,
+ @Background executor: Executor,
+ userTracker: UserTracker,
+ dumpManager: DumpManager,
+ featureFlags: FeatureFlags
+ ) : this(context, executor, ::createServiceListing, userTracker, dumpManager, featureFlags)
private var serviceListing = serviceListingBuilder(context)
// All operations in background thread
@@ -76,27 +85,25 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
private const val TAG = "ControlsListingControllerImpl"
}
- private var availableComponents = emptySet<ComponentName>()
- private var availableServices = emptyList<ServiceInfo>()
+ private var availableServices = emptyList<ControlsServiceInfo>()
private var userChangeInProgress = AtomicInteger(0)
override var currentUserId = userTracker.userId
private set
private val serviceListingCallback = ServiceListing.Callback {
- val newServices = it.toList()
- val newComponents =
- newServices.mapTo(mutableSetOf<ComponentName>(), { s -> s.getComponentName() })
-
backgroundExecutor.execute {
if (userChangeInProgress.get() > 0) return@execute
- if (!newComponents.equals(availableComponents)) {
- Log.d(TAG, "ServiceConfig reloaded, count: ${newComponents.size}")
- availableComponents = newComponents
+ Log.d(TAG, "ServiceConfig reloaded, count: ${it.size}")
+ val newServices = it.map { ControlsServiceInfo(userTracker.userContext, it) }
+ if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
+ newServices.forEach(ControlsServiceInfo::resolvePanelActivity)
+ }
+
+ if (newServices != availableServices) {
availableServices = newServices
- val currentServices = getCurrentServices()
callbacks.forEach {
- it.onServicesUpdated(currentServices)
+ it.onServicesUpdated(getCurrentServices())
}
}
}
@@ -104,6 +111,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
init {
Log.d(TAG, "Initializing")
+ dumpManager.registerDumpable(TAG, this)
serviceListing.addCallback(serviceListingCallback)
serviceListing.setListening(true)
serviceListing.reload()
@@ -165,7 +173,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
* [ControlsProviderService]
*/
override fun getCurrentServices(): List<ControlsServiceInfo> =
- availableServices.map { ControlsServiceInfo(context, it) }
+ availableServices.map(ControlsServiceInfo::copy)
/**
* Get the localized label for the component.
@@ -174,7 +182,15 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
* @return a label as returned by [CandidateInfo.loadLabel] or `null`.
*/
override fun getAppLabel(name: ComponentName): CharSequence? {
- return getCurrentServices().firstOrNull { it.componentName == name }
+ return availableServices.firstOrNull { it.componentName == name }
?.loadLabel()
}
+
+ override fun dump(writer: PrintWriter, args: Array<out String>) {
+ writer.println("ControlsListingController:")
+ writer.asIndenting().indentIfPossible {
+ println("Callbacks: $callbacks")
+ println("Services: ${getCurrentServices()}")
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index d3555eec0243..7ab36e84178e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -16,7 +16,6 @@
package com.android.systemui.dagger;
-import com.android.systemui.keyguard.KeyguardQuickAffordanceProvider;
import com.android.systemui.statusbar.QsFrameTranslateModule;
import dagger.Subcomponent;
@@ -43,9 +42,4 @@ public interface ReferenceSysUIComponent extends SysUIComponent {
interface Builder extends SysUIComponent.Builder {
ReferenceSysUIComponent build();
}
-
- /**
- * Member injection into the supplied argument.
- */
- void inject(KeyguardQuickAffordanceProvider keyguardQuickAffordanceProvider);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 99dfefa4fa41..002ffdb68682 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -146,8 +146,10 @@ object Flags {
// TODO(b/255607168): Tracking Bug
@JvmField val DOZING_MIGRATION_1 = unreleasedFlag(213, "dozing_migration_1")
+ // TODO(b/252897742): Tracking Bug
@JvmField val NEW_ELLIPSE_DETECTION = unreleasedFlag(214, "new_ellipse_detection")
+ // TODO(b/252897742): Tracking Bug
@JvmField val NEW_UDFPS_OVERLAY = unreleasedFlag(215, "new_udfps_overlay")
/**
@@ -160,6 +162,10 @@ object Flags {
val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = false)
+ /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
+ // TODO(b/240196500): Tracking Bug
+ @JvmField val ACTIVE_UNLOCK_CHIPBAR = unreleasedFlag(217, "active_unlock_chipbar")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
deleted file mode 100644
index 0f4581ce3e61..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard
-
-import android.content.ContentProvider
-import android.content.ContentValues
-import android.content.Context
-import android.content.UriMatcher
-import android.content.pm.ProviderInfo
-import android.database.Cursor
-import android.database.MatrixCursor
-import android.net.Uri
-import android.util.Log
-import com.android.systemui.SystemUIAppComponentFactoryBase
-import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
-import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
-import javax.inject.Inject
-import kotlinx.coroutines.runBlocking
-
-class KeyguardQuickAffordanceProvider :
- ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
-
- @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
-
- private lateinit var contextAvailableCallback: ContextAvailableCallback
-
- private val uriMatcher =
- UriMatcher(UriMatcher.NO_MATCH).apply {
- addURI(
- Contract.AUTHORITY,
- Contract.SlotTable.TABLE_NAME,
- MATCH_CODE_ALL_SLOTS,
- )
- addURI(
- Contract.AUTHORITY,
- Contract.AffordanceTable.TABLE_NAME,
- MATCH_CODE_ALL_AFFORDANCES,
- )
- addURI(
- Contract.AUTHORITY,
- Contract.SelectionTable.TABLE_NAME,
- MATCH_CODE_ALL_SELECTIONS,
- )
- }
-
- override fun onCreate(): Boolean {
- return true
- }
-
- override fun attachInfo(context: Context?, info: ProviderInfo?) {
- contextAvailableCallback.onContextAvailable(checkNotNull(context))
- super.attachInfo(context, info)
- }
-
- override fun setContextAvailableCallback(callback: ContextAvailableCallback) {
- contextAvailableCallback = callback
- }
-
- override fun getType(uri: Uri): String? {
- val prefix =
- when (uriMatcher.match(uri)) {
- MATCH_CODE_ALL_SLOTS,
- MATCH_CODE_ALL_AFFORDANCES,
- MATCH_CODE_ALL_SELECTIONS -> "vnd.android.cursor.dir/vnd."
- else -> null
- }
-
- val tableName =
- when (uriMatcher.match(uri)) {
- MATCH_CODE_ALL_SLOTS -> Contract.SlotTable.TABLE_NAME
- MATCH_CODE_ALL_AFFORDANCES -> Contract.AffordanceTable.TABLE_NAME
- MATCH_CODE_ALL_SELECTIONS -> Contract.SelectionTable.TABLE_NAME
- else -> null
- }
-
- if (prefix == null || tableName == null) {
- return null
- }
-
- return "$prefix${Contract.AUTHORITY}.$tableName"
- }
-
- override fun insert(uri: Uri, values: ContentValues?): Uri? {
- if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) {
- throw UnsupportedOperationException()
- }
-
- return insertSelection(values)
- }
-
- override fun query(
- uri: Uri,
- projection: Array<out String>?,
- selection: String?,
- selectionArgs: Array<out String>?,
- sortOrder: String?,
- ): Cursor? {
- return when (uriMatcher.match(uri)) {
- MATCH_CODE_ALL_AFFORDANCES -> queryAffordances()
- MATCH_CODE_ALL_SLOTS -> querySlots()
- MATCH_CODE_ALL_SELECTIONS -> querySelections()
- else -> null
- }
- }
-
- override fun update(
- uri: Uri,
- values: ContentValues?,
- selection: String?,
- selectionArgs: Array<out String>?,
- ): Int {
- Log.e(TAG, "Update is not supported!")
- return 0
- }
-
- override fun delete(
- uri: Uri,
- selection: String?,
- selectionArgs: Array<out String>?,
- ): Int {
- if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) {
- throw UnsupportedOperationException()
- }
-
- return deleteSelection(uri, selectionArgs)
- }
-
- private fun insertSelection(values: ContentValues?): Uri? {
- if (values == null) {
- throw IllegalArgumentException("Cannot insert selection, no values passed in!")
- }
-
- if (!values.containsKey(Contract.SelectionTable.Columns.SLOT_ID)) {
- throw IllegalArgumentException(
- "Cannot insert selection, " +
- "\"${Contract.SelectionTable.Columns.SLOT_ID}\" not specified!"
- )
- }
-
- if (!values.containsKey(Contract.SelectionTable.Columns.AFFORDANCE_ID)) {
- throw IllegalArgumentException(
- "Cannot insert selection, " +
- "\"${Contract.SelectionTable.Columns.AFFORDANCE_ID}\" not specified!"
- )
- }
-
- val slotId = values.getAsString(Contract.SelectionTable.Columns.SLOT_ID)
- val affordanceId = values.getAsString(Contract.SelectionTable.Columns.AFFORDANCE_ID)
-
- if (slotId.isNullOrEmpty()) {
- throw IllegalArgumentException("Cannot insert selection, slot ID was empty!")
- }
-
- if (affordanceId.isNullOrEmpty()) {
- throw IllegalArgumentException("Cannot insert selection, affordance ID was empty!")
- }
-
- val success = runBlocking {
- interactor.select(
- slotId = slotId,
- affordanceId = affordanceId,
- )
- }
-
- return if (success) {
- Log.d(TAG, "Successfully selected $affordanceId for slot $slotId")
- context?.contentResolver?.notifyChange(Contract.SelectionTable.URI, null)
- Contract.SelectionTable.URI
- } else {
- Log.d(TAG, "Failed to select $affordanceId for slot $slotId")
- null
- }
- }
-
- private fun querySelections(): Cursor {
- return MatrixCursor(
- arrayOf(
- Contract.SelectionTable.Columns.SLOT_ID,
- Contract.SelectionTable.Columns.AFFORDANCE_ID,
- )
- )
- .apply {
- val affordanceIdsBySlotId = runBlocking { interactor.getSelections() }
- affordanceIdsBySlotId.entries.forEach { (slotId, affordanceIds) ->
- affordanceIds.forEach { affordanceId ->
- addRow(
- arrayOf(
- slotId,
- affordanceId,
- )
- )
- }
- }
- }
- }
-
- private fun queryAffordances(): Cursor {
- return MatrixCursor(
- arrayOf(
- Contract.AffordanceTable.Columns.ID,
- Contract.AffordanceTable.Columns.NAME,
- Contract.AffordanceTable.Columns.ICON,
- )
- )
- .apply {
- interactor.getAffordancePickerRepresentations().forEach { representation ->
- addRow(
- arrayOf(
- representation.id,
- representation.name,
- representation.iconResourceId,
- )
- )
- }
- }
- }
-
- private fun querySlots(): Cursor {
- return MatrixCursor(
- arrayOf(
- Contract.SlotTable.Columns.ID,
- Contract.SlotTable.Columns.CAPACITY,
- )
- )
- .apply {
- interactor.getSlotPickerRepresentations().forEach { representation ->
- addRow(
- arrayOf(
- representation.id,
- representation.maxSelectedAffordances,
- )
- )
- }
- }
- }
-
- private fun deleteSelection(
- uri: Uri,
- selectionArgs: Array<out String>?,
- ): Int {
- if (selectionArgs == null) {
- throw IllegalArgumentException(
- "Cannot delete selection, selection arguments not included!"
- )
- }
-
- val (slotId, affordanceId) =
- when (selectionArgs.size) {
- 1 -> Pair(selectionArgs[0], null)
- 2 -> Pair(selectionArgs[0], selectionArgs[1])
- else ->
- throw IllegalArgumentException(
- "Cannot delete selection, selection arguments has wrong size, expected to" +
- " have 1 or 2 arguments, had ${selectionArgs.size} instead!"
- )
- }
-
- val deleted = runBlocking {
- interactor.unselect(
- slotId = slotId,
- affordanceId = affordanceId,
- )
- }
-
- return if (deleted) {
- Log.d(TAG, "Successfully unselected $affordanceId for slot $slotId")
- context?.contentResolver?.notifyChange(uri, null)
- 1
- } else {
- Log.d(TAG, "Failed to unselect $affordanceId for slot $slotId")
- 0
- }
- }
-
- companion object {
- private const val TAG = "KeyguardQuickAffordanceProvider"
- private const val MATCH_CODE_ALL_SLOTS = 1
- private const val MATCH_CODE_ALL_AFFORDANCES = 2
- private const val MATCH_CODE_ALL_SELECTIONS = 3
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index d4514c5cb7aa..783f752cbd20 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -16,9 +16,7 @@
package com.android.systemui.keyguard.data.repository
-import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
@@ -70,33 +68,15 @@ constructor(
private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
/** Determines if user is already unlocked */
val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
-
- var bouncerPromptReason: Int? = null
private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
val showMessage = _showMessage.asStateFlow()
private val _resourceUpdateRequests = MutableStateFlow(false)
val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
-
+ val bouncerPromptReason: Int
+ get() = viewMediatorCallback.bouncerPromptReason
val bouncerErrorMessage: CharSequence?
get() = viewMediatorCallback.consumeCustomMessage()
- init {
- val callback =
- object : KeyguardUpdateMonitorCallback() {
- override fun onStrongAuthStateChanged(userId: Int) {
- bouncerPromptReason = viewMediatorCallback.bouncerPromptReason
- }
-
- override fun onLockedOutStateChanged(type: BiometricSourceType) {
- if (type == BiometricSourceType.FINGERPRINT) {
- bouncerPromptReason = viewMediatorCallback.bouncerPromptReason
- }
- }
- }
-
- keyguardUpdateMonitor.registerCallback(callback)
- }
-
fun setPrimaryScrimmed(isScrimmed: Boolean) {
_primaryBouncerScrimmed.value = isScrimmed
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index ca25282ec2f0..9d5d8bbd4f40 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.repository
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.Position
@@ -69,6 +70,9 @@ interface KeyguardRepository {
*/
val isKeyguardShowing: Flow<Boolean>
+ /** Observable for the signal that keyguard is about to go away. */
+ val isKeyguardGoingAway: Flow<Boolean>
+
/** Observable for whether the bouncer is showing. */
val isBouncerShowing: Flow<Boolean>
@@ -85,6 +89,14 @@ interface KeyguardRepository {
val isDozing: Flow<Boolean>
/**
+ * Observable for whether the device is dreaming.
+ *
+ * Dozing/AOD is a specific type of dream, but it is also possible for other non-systemui dreams
+ * to be active, such as screensavers.
+ */
+ val isDreaming: Flow<Boolean>
+
+ /**
* Observable for the amount of doze we are currently in.
*
* While in doze state, this amount can change - driving a cycle of animations designed to avoid
@@ -136,12 +148,12 @@ interface KeyguardRepository {
class KeyguardRepositoryImpl
@Inject
constructor(
- statusBarStateController: StatusBarStateController,
- dozeHost: DozeHost,
- wakefulnessLifecycle: WakefulnessLifecycle,
- biometricUnlockController: BiometricUnlockController,
- private val keyguardStateController: KeyguardStateController,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ statusBarStateController: StatusBarStateController,
+ dozeHost: DozeHost,
+ wakefulnessLifecycle: WakefulnessLifecycle,
+ biometricUnlockController: BiometricUnlockController,
+ private val keyguardStateController: KeyguardStateController,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -176,6 +188,29 @@ constructor(
awaitClose { keyguardStateController.removeCallback(callback) }
}
+ override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardStateController.Callback {
+ override fun onKeyguardGoingAwayChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isKeyguardGoingAway,
+ TAG,
+ "updated isKeyguardGoingAway"
+ )
+ }
+ }
+
+ keyguardStateController.addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(
+ keyguardStateController.isKeyguardGoingAway,
+ TAG,
+ "initial isKeyguardGoingAway"
+ )
+
+ awaitClose { keyguardStateController.removeCallback(callback) }
+ }
+
override val isBouncerShowing: Flow<Boolean> = conflatedCallbackFlow {
val callback =
object : KeyguardStateController.Callback {
@@ -218,6 +253,25 @@ constructor(
}
.distinctUntilChanged()
+ override val isDreaming: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onDreamingStateChanged(isDreaming: Boolean) {
+ trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
+ }
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ trySendWithFailureLogging(
+ keyguardUpdateMonitor.isDreaming,
+ TAG,
+ "initial isDreaming",
+ )
+
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+ .distinctUntilChanged()
+
override val dozeAmount: Flow<Float> = conflatedCallbackFlow {
val callback =
object : StatusBarStateController.StateListener {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index e3d1a27dad2b..bce7d92cd8fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -94,11 +94,13 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
*/
private val _transitions =
MutableSharedFlow<TransitionStep>(
+ replay = 2,
extraBufferCapacity = 10,
- onBufferOverflow = BufferOverflow.DROP_OLDEST
+ onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
override val transitions = _transitions.asSharedFlow().distinctUntilChanged()
private var lastStep: TransitionStep = TransitionStep()
+ private var lastAnimator: ValueAnimator? = null
/*
* When manual control of the transition is requested, a unique [UUID] is used as the handle
@@ -106,19 +108,39 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
*/
private var updateTransitionId: UUID? = null
+ init {
+ // Seed with transitions signaling a boot into lockscreen state
+ emitTransition(
+ TransitionStep(
+ KeyguardState.OFF,
+ KeyguardState.LOCKSCREEN,
+ 0f,
+ TransitionState.STARTED,
+ )
+ )
+ emitTransition(
+ TransitionStep(
+ KeyguardState.OFF,
+ KeyguardState.LOCKSCREEN,
+ 1f,
+ TransitionState.FINISHED,
+ )
+ )
+ }
+
override fun startTransition(info: TransitionInfo): UUID? {
if (lastStep.transitionState != TransitionState.FINISHED) {
- // Open questions:
- // * Queue of transitions? buffer of 1?
- // * Are transitions cancellable if a new one is triggered?
- // * What validation does this need to do?
- Log.wtf(TAG, "Transition still active: $lastStep")
- return null
+ Log.i(TAG, "Transition still active: $lastStep, canceling")
}
+ val startingValue = 1f - lastStep.value
+ lastAnimator?.cancel()
+ lastAnimator = info.animator
+
info.animator?.let { animator ->
// An animator was provided, so use it to run the transition
- animator.setFloatValues(0f, 1f)
+ animator.setFloatValues(startingValue, 1f)
+ animator.duration = ((1f - startingValue) * animator.duration).toLong()
val updateListener =
object : AnimatorUpdateListener {
override fun onAnimationUpdate(animation: ValueAnimator) {
@@ -134,15 +156,24 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
val adapter =
object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator) {
- emitTransition(TransitionStep(info, 0f, TransitionState.STARTED))
+ emitTransition(TransitionStep(info, startingValue, TransitionState.STARTED))
}
override fun onAnimationCancel(animation: Animator) {
- Log.i(TAG, "Cancelling transition: $info")
+ endAnimation(animation, lastStep.value, TransitionState.CANCELED)
}
override fun onAnimationEnd(animation: Animator) {
- emitTransition(TransitionStep(info, 1f, TransitionState.FINISHED))
+ endAnimation(animation, 1f, TransitionState.FINISHED)
+ }
+
+ private fun endAnimation(
+ animation: Animator,
+ value: Float,
+ state: TransitionState
+ ) {
+ emitTransition(TransitionStep(info, value, state))
animator.removeListener(this)
animator.removeUpdateListener(updateListener)
+ lastAnimator = null
}
}
animator.addListener(adapter)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
index 0aeff7fc69fd..e5521c76705d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
@@ -20,10 +20,11 @@ import android.animation.ValueAnimator
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep
+import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -35,18 +36,30 @@ class AodLockscreenTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
- private val keyguardRepository: KeyguardRepository,
+ private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : TransitionInteractor("AOD<->LOCKSCREEN") {
override fun start() {
scope.launch {
- keyguardRepository.isDozing
- .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ /*
+ * Listening to the startedKeyguardTransitionStep (last started step) allows this code
+ * to interrupt an active transition, as long as they were either going to LOCKSCREEN or
+ * AOD state. One example is when the user presses the power button in the middle of an
+ * active transition.
+ */
+ keyguardInteractor.wakefulnessState
+ .sample(
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ { a, b -> Pair(a, b) }
+ )
.collect { pair ->
- val (isDozing, keyguardState) = pair
- if (isDozing && keyguardState == KeyguardState.LOCKSCREEN) {
+ val (wakefulnessState, lastStartedStep) = pair
+ if (
+ isSleepingOrStartingToSleep(wakefulnessState) &&
+ lastStartedStep.to == KeyguardState.LOCKSCREEN
+ ) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
@@ -55,7 +68,10 @@ constructor(
getAnimator(),
)
)
- } else if (!isDozing && keyguardState == KeyguardState.AOD) {
+ } else if (
+ isWakingOrStartingToWake(wakefulnessState) &&
+ lastStartedStep.to == KeyguardState.AOD
+ ) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
new file mode 100644
index 000000000000..dd2967334307
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 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.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class BouncerToGoneTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val shadeRepository: ShadeRepository,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor
+) : TransitionInteractor("BOUNCER->GONE") {
+
+ private var transitionId: UUID? = null
+
+ override fun start() {
+ listenForKeyguardGoingAway()
+ }
+
+ private fun listenForKeyguardGoingAway() {
+ scope.launch {
+ keyguardInteractor.isKeyguardGoingAway
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (isKeyguardGoingAway, keyguardState) = pair
+ if (isKeyguardGoingAway && keyguardState == KeyguardState.BOUNCER) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.BOUNCER,
+ to = KeyguardState.GONE,
+ animator = getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 300L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
new file mode 100644
index 000000000000..c44cda42c68d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 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.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class DreamingLockscreenTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor("DREAMING<->LOCKSCREEN") {
+
+ override fun start() {
+ scope.launch {
+ keyguardInteractor.isDreaming
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (isDreaming, keyguardState) = pair
+ if (isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.DREAMING,
+ getAnimator(),
+ )
+ )
+ } else if (!isDreaming && keyguardState == KeyguardState.DREAMING) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DREAMING,
+ KeyguardState.LOCKSCREEN,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 500L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt
new file mode 100644
index 000000000000..9e2b7241ade2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class DreamingToAodTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor("DREAMING->AOD") {
+
+ override fun start() {
+ scope.launch {
+ keyguardInteractor.wakefulnessState
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (wakefulnessState, keyguardState) = pair
+ if (
+ isSleepingOrStartingToSleep(wakefulnessState) &&
+ keyguardState == KeyguardState.DREAMING
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DREAMING,
+ KeyguardState.AOD,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 300L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 614ff8d930d8..5a1c70264ee4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -41,8 +41,15 @@ constructor(
val dozeAmount: Flow<Float> = repository.dozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
+ /**
+ * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
+ * but not vice-versa.
+ */
+ val isDreaming: Flow<Boolean> = repository.isDreaming
/** Whether the keyguard is showing or not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+ /** Whether the keyguard is going away. */
+ val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
/** Whether the bouncer is showing or not. */
val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
/** The device wake/sleep state */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 57fb4a114700..58a8093d49d2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -41,12 +41,24 @@ constructor(
}
scope.launch {
+ keyguardInteractor.isBouncerShowing.collect { logger.v("Bouncer showing", it) }
+ }
+
+ scope.launch { keyguardInteractor.isDozing.collect { logger.v("isDozing", it) } }
+
+ scope.launch {
interactor.finishedKeyguardTransitionStep.collect {
logger.i("Finished transition", it)
}
}
scope.launch {
+ interactor.canceledKeyguardTransitionStep.collect {
+ logger.i("Canceled transition", it)
+ }
+ }
+
+ scope.launch {
interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index a7c6d4450336..43dd358e4808 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -42,6 +42,9 @@ constructor(
is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it")
is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+ is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+ is DreamingLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
+ is DreamingToAodTransitionInteractor -> Log.d(TAG, "Started $it")
}
it.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 749183e241e5..54a4f493d21d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -57,6 +57,14 @@ constructor(
lockscreenToAodTransition,
)
+ /* The last [TransitionStep] with a [TransitionState] of STARTED */
+ val startedKeyguardTransitionStep: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
+
+ /* The last [TransitionStep] with a [TransitionState] of CANCELED */
+ val canceledKeyguardTransitionStep: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.transitionState == TransitionState.CANCELED }
+
/* The last [TransitionStep] with a [TransitionState] of FINISHED */
val finishedKeyguardTransitionStep: Flow<TransitionStep> =
repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
@@ -64,8 +72,4 @@ constructor(
/* The last completed [KeyguardState] transition */
val finishedKeyguardState: Flow<KeyguardState> =
finishedKeyguardTransitionStep.map { step -> step.to }
-
- /* The last [TransitionStep] with a [TransitionState] of STARTED */
- val startedKeyguardTransitionStep: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index fd4814d2bc94..cca2d566556e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -56,10 +56,20 @@ constructor(
private fun listenForBouncerHiding() {
scope.launch {
keyguardInteractor.isBouncerShowing
- .sample(keyguardInteractor.wakefulnessState, { a, b -> Pair(a, b) })
- .collect { pair ->
- val (isBouncerShowing, wakefulnessState) = pair
- if (!isBouncerShowing) {
+ .sample(
+ combine(
+ keyguardInteractor.wakefulnessState,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ ) { a, b ->
+ Pair(a, b)
+ },
+ { a, bc -> Triple(a, bc.first, bc.second) }
+ )
+ .collect { triple ->
+ val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
+ if (
+ !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
+ ) {
val to =
if (
wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP ||
@@ -90,10 +100,10 @@ constructor(
combine(
keyguardTransitionInteractor.finishedKeyguardState,
keyguardInteractor.statusBarState,
- ) { keyguardState, statusBarState ->
- Pair(keyguardState, statusBarState)
+ ) { a, b ->
+ Pair(a, b)
},
- { shadeModel, pair -> Triple(shadeModel, pair.first, pair.second) }
+ { a, bc -> Triple(a, bc.first, bc.second) }
)
.collect { triple ->
val (shadeModel, keyguardState, statusBarState) = triple
@@ -116,8 +126,7 @@ constructor(
)
} else {
// TODO (b/251849525): Remove statusbarstate check when that state is
- // integrated
- // into KeyguardTransitionRepository
+ // integrated into KeyguardTransitionRepository
if (
keyguardState == KeyguardState.LOCKSCREEN &&
shadeModel.isUserDragging &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
index 6c1adbd68ef2..4100f7a8413a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
@@ -23,6 +23,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
@@ -34,23 +35,27 @@ class LockscreenGoneTransitionInteractor
constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
) : TransitionInteractor("LOCKSCREEN->GONE") {
override fun start() {
scope.launch {
- keyguardInteractor.isKeyguardShowing.collect { isShowing ->
- if (!isShowing) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- name,
- KeyguardState.LOCKSCREEN,
- KeyguardState.GONE,
- getAnimator(),
+ keyguardInteractor.isKeyguardGoingAway
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (isKeyguardGoingAway, keyguardState) = pair
+ if (!isKeyguardGoingAway && keyguardState == KeyguardState.LOCKSCREEN) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
)
- )
+ }
}
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index c22f4e7a2634..fcd653b33a6f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -210,9 +210,12 @@ constructor(
expansion == KeyguardBouncer.EXPANSION_HIDDEN &&
oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN
) {
- repository.setPrimaryVisible(false)
- repository.setPrimaryShow(null)
- falsingCollector.onBouncerHidden()
+ /*
+ * There are cases where #hide() was not invoked, such as when
+ * NotificationPanelViewController controls the hide animation. Make sure the state gets
+ * updated by calling #hide() directly.
+ */
+ hide()
DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() }
primaryBouncerCallbackInteractor.dispatchFullyHidden()
} else if (
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 37f33afbf53e..dbffeab436a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -46,5 +46,19 @@ abstract class StartKeyguardTransitionModule {
@Binds
@IntoSet
+ abstract fun bouncerGone(impl: BouncerToGoneTransitionInteractor): TransitionInteractor
+
+ @Binds
+ @IntoSet
abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
+
+ @Binds
+ @IntoSet
+ abstract fun dreamingLockscreen(
+ impl: DreamingLockscreenTransitionInteractor
+ ): TransitionInteractor
+
+ @Binds
+ @IntoSet
+ abstract fun dreamingToAod(impl: DreamingToAodTransitionInteractor): TransitionInteractor
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 7958033ba017..dd908c420fcb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -17,12 +17,29 @@ package com.android.systemui.keyguard.shared.model
/** List of all possible states to transition to/from */
enum class KeyguardState {
+ /*
+ * The display is completely off, as well as any sensors that would trigger the device to wake
+ * up.
+ */
+ OFF,
/**
- * For initialization as well as when the security method is set to NONE, indicating that
- * the keyguard should never be shown.
+ * The device has entered a special low-power mode within SystemUI. Doze is technically a
+ * special dream service implementation. No UI is visible. In this state, a least some
+ * low-powered sensors such as lift to wake or tap to wake are enabled, or wake screen for
+ * notifications is enabled, allowing the device to quickly wake up.
+ */
+ DOZING,
+ /*
+ * A device state after the device times out, which can be from both LOCKSCREEN or GONE states.
+ * DOZING is an example of special version of this state. Dreams may be implemented by third
+ * parties to present their own UI over keyguard, like a screensaver.
+ */
+ DREAMING,
+ /**
+ * The device has entered a special low-power mode within SystemUI, also called the Always-on
+ * Display (AOD). A minimal UI is presented to show critical information. If the device is in
+ * low-power mode without a UI, then it is DOZING.
*/
- NONE,
- /* Always-on Display. The device is in a low-power mode with a minimal UI visible */
AOD,
/*
* The security screen prompt UI, containing PIN, Password, Pattern, and all FPS
@@ -34,7 +51,6 @@ enum class KeyguardState {
* unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
*/
LOCKSCREEN,
-
/*
* Keyguard is no longer visible. In most cases the user has just authenticated and keyguard
* is being removed, but there are other cases where the user is swiping away keyguard, such as
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
index 0e0465bb5207..38a93b50ea97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
@@ -17,7 +17,12 @@ package com.android.systemui.keyguard.shared.model
/** Possible states for a running transition between [State] */
enum class TransitionState {
+ /* Transition has begun. */
STARTED,
+ /* Transition is actively running. */
RUNNING,
- FINISHED
+ /* Transition has completed successfully. */
+ FINISHED,
+ /* Transition has been interrupted, and not completed successfully. */
+ CANCELED,
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
index 732a6f7b887a..767fd58f78e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -17,8 +17,8 @@ package com.android.systemui.keyguard.shared.model
/** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */
data class TransitionStep(
- val from: KeyguardState = KeyguardState.NONE,
- val to: KeyguardState = KeyguardState.NONE,
+ val from: KeyguardState = KeyguardState.OFF,
+ val to: KeyguardState = KeyguardState.OFF,
val value: Float = 0f, // constrained [0.0, 1.0]
val transitionState: TransitionState = TransitionState.FINISHED,
val ownerName: String = "",
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
index 64f834d6c5ab..92040f4f0348 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -24,5 +24,15 @@ enum class WakefulnessModel {
/** Device is now fully awake and interactive. */
AWAKE,
/** Signal that the device is now going to sleep. */
- STARTING_TO_SLEEP,
+ STARTING_TO_SLEEP;
+
+ companion object {
+ fun isSleepingOrStartingToSleep(model: WakefulnessModel): Boolean {
+ return model == ASLEEP || model == STARTING_TO_SLEEP
+ }
+
+ fun isWakingOrStartingToWake(model: WakefulnessModel): Boolean {
+ return model == AWAKE || model == STARTING_TO_WAKE
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index a22958b74bb9..7739a456fcb7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -94,6 +94,10 @@ object KeyguardBouncerViewBinder {
viewModel.setBouncerViewDelegate(delegate)
launch {
viewModel.show.collect {
+ hostViewController.showPromptReason(it.promptReason)
+ it.errorMessage?.let { errorMessage ->
+ hostViewController.showErrorMessage(errorMessage)
+ }
hostViewController.showPrimarySecurityScreen()
hostViewController.appear(
SystemBarUtils.getStatusBarHeight(view.context)
@@ -102,18 +106,6 @@ object KeyguardBouncerViewBinder {
}
launch {
- viewModel.showPromptReason.collect { prompt ->
- hostViewController.showPromptReason(prompt)
- }
- }
-
- launch {
- viewModel.showBouncerErrorMessage.collect { errorMessage ->
- hostViewController.showErrorMessage(errorMessage)
- }
- }
-
- launch {
viewModel.showWithFullExpansion.collect { model ->
hostViewController.resetSecurityContainer()
hostViewController.showPromptReason(model.promptReason)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 07816001f45c..526ae741793c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -26,7 +26,6 @@ import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
/** Models UI state for the lock screen bouncer; handles user input. */
@@ -45,13 +44,6 @@ constructor(
/** Observe whether bouncer is showing. */
val show: Flow<KeyguardBouncerModel> = interactor.show
- /** Observe bouncer prompt when bouncer is showing. */
- val showPromptReason: Flow<Int> = interactor.show.map { it.promptReason }
-
- /** Observe bouncer error message when bouncer is showing. */
- val showBouncerErrorMessage: Flow<CharSequence> =
- interactor.show.map { it.errorMessage }.filterNotNull()
-
/** Observe visible expansion when bouncer is showing. */
val showWithFullExpansion: Flow<KeyguardBouncerModel> =
interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
index b682bd172837..d4991f90a86b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
@@ -148,6 +148,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
val currentRotation: Int = display.rotation
val displayWidthPx = windowMetrics.bounds.width()
+ val displayHeightPx = windowMetrics.bounds.height()
val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
val isTablet = isTablet(context)
val taskbarSize =
@@ -163,6 +164,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
measuredWidth,
measuredHeight,
displayWidthPx,
+ displayHeightPx,
taskbarSize,
isTablet,
currentRotation,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 920a108382fb..d9be2810d165 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -515,7 +515,13 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
public void setExpanded(boolean expanded) {
if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
mQsExpanded = expanded;
- updateQsPanelControllerListening();
+ if (mInSplitShade && mQsExpanded) {
+ // in split shade QS is expanded immediately when shade expansion starts and then we
+ // also need to listen to changes - otherwise QS is updated only once its fully expanded
+ setListening(true);
+ } else {
+ updateQsPanelControllerListening();
+ }
updateQsState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index cffd28f9fc96..19bb15a5c2d8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -24,8 +24,9 @@ import android.os.Handler
import android.os.Looper
import android.os.ResultReceiver
import android.view.View
+import android.view.View.GONE
+import android.view.View.VISIBLE
import android.widget.AdapterView
-import android.widget.AdapterView.OnItemClickListener
import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.Switch
@@ -47,6 +48,7 @@ class ScreenRecordPermissionDialog(
private val onStartRecordingClicked: Runnable?
) : BaseScreenSharePermissionDialog(context, createOptionList(), null) {
private lateinit var tapsSwitch: Switch
+ private lateinit var tapsView: View
private lateinit var audioSwitch: Switch
private lateinit var options: Spinner
override fun onCreate(savedInstanceState: Bundle?) {
@@ -84,16 +86,25 @@ class ScreenRecordPermissionDialog(
private fun initRecordOptionsView() {
audioSwitch = findViewById(R.id.screenrecord_audio_switch)
tapsSwitch = findViewById(R.id.screenrecord_taps_switch)
+ tapsView = findViewById(R.id.show_taps)
+ updateTapsViewVisibility()
options = findViewById(R.id.screen_recording_options)
val a: ArrayAdapter<*> =
ScreenRecordingAdapter(context, android.R.layout.simple_spinner_dropdown_item, MODES)
a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
options.adapter = a
- options.setOnItemClickListenerInt(
- OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, _: Long ->
- audioSwitch.isChecked = true
- }
- )
+ options.setOnItemClickListenerInt { _: AdapterView<*>?, _: View?, _: Int, _: Long ->
+ audioSwitch.isChecked = true
+ }
+ }
+
+ override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) {
+ super.onItemSelected(adapterView, view, pos, id)
+ updateTapsViewVisibility()
+ }
+
+ private fun updateTapsViewVisibility() {
+ tapsView.visibility = if (selectedScreenShareOption.mode == SINGLE_APP) GONE else VISIBLE
}
/**
@@ -103,7 +114,7 @@ class ScreenRecordPermissionDialog(
*/
private fun requestScreenCapture(captureTarget: MediaProjectionCaptureTarget?) {
val userContext = userContextProvider.userContext
- val showTaps = tapsSwitch.isChecked
+ val showTaps = selectedScreenShareOption.mode != SINGLE_APP && tapsSwitch.isChecked
val audioMode =
if (audioSwitch.isChecked) options.selectedItem as ScreenRecordingAudioSource
else ScreenRecordingAudioSource.NONE
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 5961635a0dba..01e32b7ada5f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -32,7 +32,7 @@ import android.view.WindowManagerGlobal
import com.android.internal.infra.ServiceConnector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
@@ -45,7 +45,7 @@ class ActionIntentExecutor
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- @Background private val bgDispatcher: CoroutineDispatcher,
+ @Main private val mainDispatcher: CoroutineDispatcher,
private val context: Context,
) {
/**
@@ -70,23 +70,21 @@ constructor(
userId: Int,
overrideTransition: Boolean,
) {
- withContext(bgDispatcher) {
- dismissKeyguard()
+ dismissKeyguard()
- if (userId == UserHandle.myUserId()) {
- context.startActivity(intent, bundle)
- } else {
- launchCrossProfileIntent(userId, intent, bundle)
- }
+ if (userId == UserHandle.myUserId()) {
+ withContext(mainDispatcher) { context.startActivity(intent, bundle) }
+ } else {
+ launchCrossProfileIntent(userId, intent, bundle)
+ }
- if (overrideTransition) {
- val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
- try {
- WindowManagerGlobal.getWindowManagerService()
- .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY)
- } catch (e: Exception) {
- Log.e(TAG, "Error overriding screenshot app transition", e)
- }
+ if (overrideTransition) {
+ val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY)
+ } catch (e: Exception) {
+ Log.e(TAG, "Error overriding screenshot app transition", e)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 8bf956b86683..5450db98af52 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -46,6 +46,8 @@ import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot;
import com.google.common.util.concurrent.ListenableFuture;
@@ -67,6 +69,7 @@ public class LongScreenshotActivity extends Activity {
private static final String TAG = LogConfig.logTag(LongScreenshotActivity.class);
public static final String EXTRA_CAPTURE_RESPONSE = "capture-response";
+ public static final String EXTRA_SCREENSHOT_USER_HANDLE = "screenshot-userhandle";
private static final String KEY_SAVED_IMAGE_PATH = "saved-image-path";
private final UiEventLogger mUiEventLogger;
@@ -74,6 +77,8 @@ public class LongScreenshotActivity extends Activity {
private final Executor mBackgroundExecutor;
private final ImageExporter mImageExporter;
private final LongScreenshotData mLongScreenshotHolder;
+ private final ActionIntentExecutor mActionExecutor;
+ private final FeatureFlags mFeatureFlags;
private ImageView mPreview;
private ImageView mTransitionView;
@@ -85,6 +90,7 @@ public class LongScreenshotActivity extends Activity {
private CropView mCropView;
private MagnifierView mMagnifierView;
private ScrollCaptureResponse mScrollCaptureResponse;
+ private UserHandle mScreenshotUserHandle;
private File mSavedImagePath;
private ListenableFuture<File> mCacheSaveFuture;
@@ -103,12 +109,15 @@ public class LongScreenshotActivity extends Activity {
@Inject
public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter,
@Main Executor mainExecutor, @Background Executor bgExecutor,
- LongScreenshotData longScreenshotHolder) {
+ LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor,
+ FeatureFlags featureFlags) {
mUiEventLogger = uiEventLogger;
mUiExecutor = mainExecutor;
mBackgroundExecutor = bgExecutor;
mImageExporter = imageExporter;
mLongScreenshotHolder = longScreenshotHolder;
+ mActionExecutor = actionExecutor;
+ mFeatureFlags = featureFlags;
}
@@ -139,6 +148,11 @@ public class LongScreenshotActivity extends Activity {
Intent intent = getIntent();
mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE);
+ mScreenshotUserHandle = intent.getParcelableExtra(EXTRA_SCREENSHOT_USER_HANDLE,
+ UserHandle.class);
+ if (mScreenshotUserHandle == null) {
+ mScreenshotUserHandle = Process.myUserHandle();
+ }
if (savedInstanceState != null) {
String savedImagePath = savedInstanceState.getString(KEY_SAVED_IMAGE_PATH);
@@ -318,36 +332,51 @@ public class LongScreenshotActivity extends Activity {
}
private void doEdit(Uri uri) {
- String editorPackage = getString(R.string.config_screenshotEditor);
- Intent intent = new Intent(Intent.ACTION_EDIT);
- if (!TextUtils.isEmpty(editorPackage)) {
- intent.setComponent(ComponentName.unflattenFromString(editorPackage));
+ if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY) && mScreenshotUserHandle
+ != Process.myUserHandle()) {
+ // TODO: Fix transition for work profile. Omitting it in the meantime.
+ mActionExecutor.launchIntentAsync(
+ ActionIntentCreator.INSTANCE.createEditIntent(uri, this),
+ null,
+ mScreenshotUserHandle.getIdentifier(), false);
+ } else {
+ String editorPackage = getString(R.string.config_screenshotEditor);
+ Intent intent = new Intent(Intent.ACTION_EDIT);
+ if (!TextUtils.isEmpty(editorPackage)) {
+ intent.setComponent(ComponentName.unflattenFromString(editorPackage));
+ }
+ intent.setDataAndType(uri, "image/png");
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ mTransitionView.setImageBitmap(mOutputBitmap);
+ mTransitionView.setVisibility(View.VISIBLE);
+ mTransitionView.setTransitionName(
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
+ // TODO: listen for transition completing instead of finishing onStop
+ mTransitionStarted = true;
+ startActivity(intent,
+ ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle());
}
- intent.setDataAndType(uri, "image/png");
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
- mTransitionView.setImageBitmap(mOutputBitmap);
- mTransitionView.setVisibility(View.VISIBLE);
- mTransitionView.setTransitionName(
- ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
- // TODO: listen for transition completing instead of finishing onStop
- mTransitionStarted = true;
- startActivity(intent,
- ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
- ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle());
}
private void doShare(Uri uri) {
- Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType("image/png");
- intent.putExtra(Intent.EXTRA_STREAM, uri);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
- | Intent.FLAG_GRANT_READ_URI_PERMISSION);
- Intent sharingChooserIntent = Intent.createChooser(intent, null)
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
+ if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+ Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri, null);
+ mActionExecutor.launchIntentAsync(shareIntent, null,
+ mScreenshotUserHandle.getIdentifier(), false);
+ } else {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("image/png");
+ intent.putExtra(Intent.EXTRA_STREAM, uri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ Intent sharingChooserIntent = Intent.createChooser(intent, null)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
+ }
}
private void onClicked(View v) {
@@ -389,8 +418,8 @@ public class LongScreenshotActivity extends Activity {
mOutputBitmap = renderBitmap(drawable, bounds);
ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now(),
- // TODO: Owner must match the owner of the captured window.
- Process.myUserHandle());
+ mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)
+ ? mScreenshotUserHandle : Process.myUserHandle());
exportFuture.addListener(() -> onExportCompleted(action, exportFuture), mUiExecutor);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index d395bd33241d..d94c8277b82c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -591,7 +591,7 @@ public class ScreenshotController {
// Wait until this window is attached to request because it is
// the reference used to locate the target window (below).
withWindowAttached(() -> {
- requestScrollCapture();
+ requestScrollCapture(owner);
mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
new ViewRootImpl.ActivityConfigCallback() {
@Override
@@ -603,11 +603,11 @@ public class ScreenshotController {
mScreenshotView.hideScrollChip();
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
- mScreenshotHandler.postDelayed(
- ScreenshotController.this::requestScrollCapture, 150);
+ mScreenshotHandler.postDelayed(() -> {
+ requestScrollCapture(owner);
+ }, 150);
mScreenshotView.updateInsets(
- mWindowManager.getCurrentWindowMetrics()
- .getWindowInsets());
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets());
// Screenshot animation calculations won't be valid anymore,
// so just end
if (mScreenshotAnimation != null
@@ -655,7 +655,7 @@ public class ScreenshotController {
mScreenshotHandler.cancelTimeout(); // restarted after animation
}
- private void requestScrollCapture() {
+ private void requestScrollCapture(UserHandle owner) {
if (!allowLongScreenshots()) {
Log.d(TAG, "Long screenshots not supported on this device");
return;
@@ -668,10 +668,11 @@ public class ScreenshotController {
mScrollCaptureClient.request(DEFAULT_DISPLAY);
mLastScrollCaptureRequest = future;
mLastScrollCaptureRequest.addListener(() ->
- onScrollCaptureResponseReady(future), mMainExecutor);
+ onScrollCaptureResponseReady(future, owner), mMainExecutor);
}
- private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture) {
+ private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture,
+ UserHandle owner) {
try {
if (mLastScrollCaptureResponse != null) {
mLastScrollCaptureResponse.close();
@@ -701,7 +702,7 @@ public class ScreenshotController {
mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
mScreenshotTakenInPortrait);
// delay starting scroll capture to make sure the scrim is up before the app moves
- mScreenshotView.post(() -> runBatchScrollCapture(response));
+ mScreenshotView.post(() -> runBatchScrollCapture(response, owner));
});
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "requestScrollCapture failed", e);
@@ -710,7 +711,7 @@ public class ScreenshotController {
ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture;
- private void runBatchScrollCapture(ScrollCaptureResponse response) {
+ private void runBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
// Clear the reference to prevent close() in dismissScreenshot
mLastScrollCaptureResponse = null;
@@ -744,6 +745,8 @@ public class ScreenshotController {
longScreenshot));
final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
+ intent.putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE,
+ owner);
intent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index c41e2bc14afc..4cb91e134003 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -15,12 +15,17 @@
*/
package com.android.systemui.screenshot
-import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
+import androidx.lifecycle.LifecycleService
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.phone.CentralSurfaces
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.util.Optional
import javax.inject.Inject
@@ -30,7 +35,8 @@ import javax.inject.Inject
internal class ScreenshotProxyService @Inject constructor(
private val mExpansionMgr: ShadeExpansionStateManager,
private val mCentralSurfacesOptional: Optional<CentralSurfaces>,
-) : Service() {
+ @Main private val mMainDispatcher: CoroutineDispatcher,
+) : LifecycleService() {
private val mBinder: IBinder = object : IScreenshotProxy.Stub() {
/**
@@ -43,20 +49,28 @@ internal class ScreenshotProxyService @Inject constructor(
}
override fun dismissKeyguard(callback: IOnDoneCallback) {
- if (mCentralSurfacesOptional.isPresent) {
- mCentralSurfacesOptional.get().executeRunnableDismissingKeyguard(
- Runnable {
- callback.onDone(true)
- }, null,
- true /* dismissShade */, true /* afterKeyguardGone */,
- true /* deferred */
- )
- } else {
- callback.onDone(false)
+ lifecycleScope.launch {
+ executeAfterDismissing(callback)
}
}
}
+ private suspend fun executeAfterDismissing(callback: IOnDoneCallback) =
+ withContext(mMainDispatcher) {
+ mCentralSurfacesOptional.ifPresentOrElse(
+ {
+ it.executeRunnableDismissingKeyguard(
+ Runnable {
+ callback.onDone(true)
+ }, null,
+ true /* dismissShade */, true /* afterKeyguardGone */,
+ true /* deferred */
+ )
+ },
+ { callback.onDone(false) }
+ )
+ }
+
override fun onBind(intent: Intent): IBinder? {
Log.d(TAG, "onBind: $intent")
return mBinder
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java b/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java
new file mode 100644
index 000000000000..fc61e90ab8f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 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.shade;
+
+import com.android.systemui.camera.CameraGestureHelper;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+
+import javax.inject.Inject;
+
+/** Handles launching camera from Shade. */
+@SysUISingleton
+public class CameraLauncher {
+ private final CameraGestureHelper mCameraGestureHelper;
+ private final KeyguardBypassController mKeyguardBypassController;
+
+ private boolean mLaunchingAffordance;
+
+ @Inject
+ public CameraLauncher(
+ CameraGestureHelper cameraGestureHelper,
+ KeyguardBypassController keyguardBypassController
+ ) {
+ mCameraGestureHelper = cameraGestureHelper;
+ mKeyguardBypassController = keyguardBypassController;
+ }
+
+ /** Launches the camera. */
+ public void launchCamera(int source, boolean isShadeFullyCollapsed) {
+ if (!isShadeFullyCollapsed) {
+ setLaunchingAffordance(true);
+ }
+
+ mCameraGestureHelper.launchCamera(source);
+ }
+
+ /**
+ * Set whether we are currently launching an affordance. This is currently only set when
+ * launched via a camera gesture.
+ */
+ public void setLaunchingAffordance(boolean launchingAffordance) {
+ mLaunchingAffordance = launchingAffordance;
+ mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
+ }
+
+ /**
+ * Return true when a bottom affordance is launching an occluded activity with a splash screen.
+ */
+ public boolean isLaunchingAffordance() {
+ return mLaunchingAffordance;
+ }
+
+ /**
+ * Whether the camera application can be launched for the camera launch gesture.
+ */
+ public boolean canCameraGestureBeLaunched(int barState) {
+ return mCameraGestureHelper.canCameraGestureBeLaunched(barState);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 8c1b5746f61e..ceef8c8ff31c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -124,7 +124,6 @@ import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.DisplayId;
@@ -464,7 +463,6 @@ public final class NotificationPanelViewController implements Dumpable {
private boolean mCollapsedOnDown;
private boolean mClosingWithAlphaFadeOut;
private boolean mHeadsUpAnimatingAway;
- private boolean mLaunchingAffordance;
private final FalsingManager mFalsingManager;
private final FalsingCollector mFalsingCollector;
@@ -575,7 +573,7 @@ public final class NotificationPanelViewController implements Dumpable {
/** Whether the current animator is resetting the pulse expansion after a drag down. */
private boolean mIsPulseExpansionResetAnimator;
- private final Rect mKeyguardStatusAreaClipBounds = new Rect();
+ private final Rect mLastQsClipBounds = new Rect();
private final Region mQsInterceptRegion = new Region();
/** Alpha of the views which only show on the keyguard but not in shade / shade locked. */
private float mKeyguardOnlyContentAlpha = 1.0f;
@@ -615,7 +613,6 @@ public final class NotificationPanelViewController implements Dumpable {
private final NotificationListContainer mNotificationListContainer;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final NPVCDownEventState.Buffer mLastDownEvents;
- private final CameraGestureHelper mCameraGestureHelper;
private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
private float mMinExpandHeight;
@@ -743,7 +740,6 @@ public final class NotificationPanelViewController implements Dumpable {
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
ShadeTransitionController shadeTransitionController,
SystemClock systemClock,
- CameraGestureHelper cameraGestureHelper,
KeyguardBottomAreaViewModel keyguardBottomAreaViewModel,
KeyguardBottomAreaInteractor keyguardBottomAreaInteractor,
DumpManager dumpManager) {
@@ -924,7 +920,6 @@ public final class NotificationPanelViewController implements Dumpable {
unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay);
}
});
- mCameraGestureHelper = cameraGestureHelper;
mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
dumpManager.registerDumpable(this);
}
@@ -2814,7 +2809,7 @@ public final class NotificationPanelViewController implements Dumpable {
*/
private void applyQSClippingBounds(int left, int top, int right, int bottom,
boolean qsVisible) {
- if (!mAnimateNextNotificationBounds || mKeyguardStatusAreaClipBounds.isEmpty()) {
+ if (!mAnimateNextNotificationBounds || mLastQsClipBounds.isEmpty()) {
if (mQsClippingAnimation != null) {
// update the end position of the animator
mQsClippingAnimationEndBounds.set(left, top, right, bottom);
@@ -2823,10 +2818,10 @@ public final class NotificationPanelViewController implements Dumpable {
}
} else {
mQsClippingAnimationEndBounds.set(left, top, right, bottom);
- final int startLeft = mKeyguardStatusAreaClipBounds.left;
- final int startTop = mKeyguardStatusAreaClipBounds.top;
- final int startRight = mKeyguardStatusAreaClipBounds.right;
- final int startBottom = mKeyguardStatusAreaClipBounds.bottom;
+ final int startLeft = mLastQsClipBounds.left;
+ final int startTop = mLastQsClipBounds.top;
+ final int startRight = mLastQsClipBounds.right;
+ final int startBottom = mLastQsClipBounds.bottom;
if (mQsClippingAnimation != null) {
mQsClippingAnimation.cancel();
}
@@ -2863,12 +2858,10 @@ public final class NotificationPanelViewController implements Dumpable {
private void applyQSClippingImmediately(int left, int top, int right, int bottom,
boolean qsVisible) {
- // Fancy clipping for quick settings
int radius = mScrimCornerRadius;
boolean clipStatusView = false;
+ mLastQsClipBounds.set(left, top, right, bottom);
if (mIsFullWidth) {
- // The padding on this area is large enough that we can use a cheaper clipping strategy
- mKeyguardStatusAreaClipBounds.set(left, top, right, bottom);
clipStatusView = qsVisible;
float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius;
radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
@@ -2903,8 +2896,8 @@ public final class NotificationPanelViewController implements Dumpable {
radius,
qsVisible && !mSplitShadeEnabled);
}
- mKeyguardStatusViewController.setClipBounds(
- clipStatusView ? mKeyguardStatusAreaClipBounds : null);
+ // The padding on this area is large enough that we can use a cheaper clipping strategy
+ mKeyguardStatusViewController.setClipBounds(clipStatusView ? mLastQsClipBounds : null);
if (!qsVisible && mSplitShadeEnabled) {
// On the lockscreen when qs isn't visible, we don't want the bounds of the shade to
// be visible, otherwise you can see the bounds once swiping up to see bouncer
@@ -3948,6 +3941,10 @@ public final class NotificationPanelViewController implements Dumpable {
}
}
+ public int getBarState() {
+ return mBarState;
+ }
+
private boolean isOnKeyguard() {
return mBarState == KEYGUARD;
}
@@ -3993,35 +3990,6 @@ public final class NotificationPanelViewController implements Dumpable {
&& mBarState == StatusBarState.SHADE;
}
- /** Launches the camera. */
- public void launchCamera(int source) {
- if (!isFullyCollapsed()) {
- setLaunchingAffordance(true);
- }
-
- mCameraGestureHelper.launchCamera(source);
- }
-
- public void onAffordanceLaunchEnded() {
- setLaunchingAffordance(false);
- }
-
- /** Set whether we are currently launching an affordance (i.e. camera gesture). */
- private void setLaunchingAffordance(boolean launchingAffordance) {
- mLaunchingAffordance = launchingAffordance;
- mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
- }
-
- /** Returns whether a bottom affordance is launching an occluded activity with splash screen. */
- public boolean isLaunchingAffordanceWithPreview() {
- return mLaunchingAffordance;
- }
-
- /** Whether the camera application can be launched by the camera launch gesture. */
- public boolean canCameraGestureBeLaunched() {
- return mCameraGestureHelper.canCameraGestureBeLaunched(mBarState);
- }
-
public boolean hideStatusBarIconsWhenExpanded() {
if (mIsLaunchAnimationRunning) {
return mHideIconsDuringLaunchAnimation;
@@ -4360,7 +4328,6 @@ public final class NotificationPanelViewController implements Dumpable {
ipw.print("mCollapsedOnDown="); ipw.println(mCollapsedOnDown);
ipw.print("mClosingWithAlphaFadeOut="); ipw.println(mClosingWithAlphaFadeOut);
ipw.print("mHeadsUpAnimatingAway="); ipw.println(mHeadsUpAnimatingAway);
- ipw.print("mLaunchingAffordance="); ipw.println(mLaunchingAffordance);
ipw.print("mShowIconsWhenExpanded="); ipw.println(mShowIconsWhenExpanded);
ipw.print("mIndicationBottomPadding="); ipw.println(mIndicationBottomPadding);
ipw.print("mAmbientIndicationBottomPadding="); ipw.println(mAmbientIndicationBottomPadding);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 3670d0987a03..0deb47d73460 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -1263,7 +1263,8 @@ public class KeyguardIndicationController {
showErrorMessageNowOrLater(errString, followupMessage);
} else if (!mAuthController.isUdfpsFingerDown()) {
// On subsequent lockouts, we show a more generic locked out message.
- showBiometricMessage(mContext.getString(R.string.keyguard_face_unlock_unavailable),
+ showErrorMessageNowOrLater(
+ mContext.getString(R.string.keyguard_face_unlock_unavailable),
followupMessage);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
index ec221b7eccc0..c523d22456f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
@@ -15,9 +15,7 @@
*/
package com.android.systemui.statusbar.connectivity;
-import static com.android.settingslib.mobile.MobileMappings.getDefaultIcons;
-import static com.android.settingslib.mobile.MobileMappings.getIconKey;
-import static com.android.settingslib.mobile.MobileMappings.mapIconSets;
+import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
import android.content.Context;
import android.content.Intent;
@@ -46,6 +44,7 @@ import com.android.settingslib.mobile.MobileStatusTracker.SubscriptionDefaults;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.SignalStrengthUtil;
import com.android.systemui.R;
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy;
import com.android.systemui.util.CarrierConfigTracker;
import java.io.PrintWriter;
@@ -63,6 +62,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
private final TelephonyManager mPhone;
private final CarrierConfigTracker mCarrierConfigTracker;
private final SubscriptionDefaults mDefaults;
+ private final MobileMappingsProxy mMobileMappingsProxy;
private final String mNetworkNameDefault;
private final String mNetworkNameSeparator;
private final ContentObserver mObserver;
@@ -121,6 +121,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
TelephonyManager phone,
CallbackHandler callbackHandler,
NetworkControllerImpl networkController,
+ MobileMappingsProxy mobileMappingsProxy,
SubscriptionInfo info,
SubscriptionDefaults defaults,
Looper receiverLooper,
@@ -135,13 +136,14 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
mPhone = phone;
mDefaults = defaults;
mSubscriptionInfo = info;
+ mMobileMappingsProxy = mobileMappingsProxy;
mNetworkNameSeparator = getTextIfExists(
R.string.status_bar_network_name_separator).toString();
mNetworkNameDefault = getTextIfExists(
com.android.internal.R.string.lockscreen_carrier_default).toString();
- mNetworkToIconLookup = mapIconSets(mConfig);
- mDefaultIcons = getDefaultIcons(mConfig);
+ mNetworkToIconLookup = mMobileMappingsProxy.mapIconSets(mConfig);
+ mDefaultIcons = mMobileMappingsProxy.getDefaultIcons(mConfig);
String networkName = info.getCarrierName() != null ? info.getCarrierName().toString()
: mNetworkNameDefault;
@@ -161,8 +163,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
void setConfiguration(Config config) {
mConfig = config;
updateInflateSignalStrength();
- mNetworkToIconLookup = mapIconSets(mConfig);
- mDefaultIcons = getDefaultIcons(mConfig);
+ mNetworkToIconLookup = mMobileMappingsProxy.mapIconSets(mConfig);
+ mDefaultIcons = mMobileMappingsProxy.getDefaultIcons(mConfig);
updateTelephony();
}
@@ -271,8 +273,9 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
dataContentDescription = mContext.getString(R.string.data_connection_no_internet);
}
- final QsInfo qsInfo = getQsInfo(contentDescription, icons.dataType);
- final SbInfo sbInfo = getSbInfo(contentDescription, icons.dataType);
+ int iconId = mCurrentState.getNetworkTypeIcon(mContext);
+ final QsInfo qsInfo = getQsInfo(contentDescription, iconId);
+ final SbInfo sbInfo = getSbInfo(contentDescription, iconId);
MobileDataIndicators mobileDataIndicators = new MobileDataIndicators(
sbInfo.icon,
@@ -373,6 +376,10 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
} else if (action.equals(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
updateDataSim();
notifyListenersIfNecessary();
+ } else if (action.equals(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED)) {
+ int carrierId = intent.getIntExtra(
+ TelephonyManager.EXTRA_CARRIER_ID, UNKNOWN_CARRIER_ID);
+ mCurrentState.setCarrierId(carrierId);
}
}
@@ -477,7 +484,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
mCurrentState.level = getSignalLevel(mCurrentState.signalStrength);
}
- String iconKey = getIconKey(mCurrentState.telephonyDisplayInfo);
+ mCurrentState.setCarrierId(mPhone.getSimCarrierId());
+ String iconKey = mMobileMappingsProxy.getIconKey(mCurrentState.telephonyDisplayInfo);
if (mNetworkToIconLookup.get(iconKey) != null) {
mCurrentState.iconGroup = mNetworkToIconLookup.get(iconKey);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
index 793817948803..a323454a7ed8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
@@ -22,6 +22,7 @@ import android.telephony.TelephonyManager
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileStatusTracker
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject
@@ -33,6 +34,7 @@ internal class MobileSignalControllerFactory @Inject constructor(
val context: Context,
val callbackHandler: CallbackHandler,
val carrierConfigTracker: CarrierConfigTracker,
+ val mobileMappings: MobileMappingsProxy,
) {
fun createMobileSignalController(
config: MobileMappings.Config,
@@ -56,6 +58,7 @@ internal class MobileSignalControllerFactory @Inject constructor(
phone,
callbackHandler,
networkController,
+ mobileMappings,
subscriptionInfo,
subscriptionDefaults,
receiverLooper,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
index f20d20631c95..1fb6a982fdf3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
@@ -16,10 +16,14 @@
package com.android.systemui.statusbar.connectivity
+import android.annotation.DrawableRes
+import android.content.Context
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
+import com.android.internal.annotations.VisibleForTesting
+import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.Utils
import com.android.settingslib.mobile.MobileStatusTracker.MobileStatus
import com.android.settingslib.mobile.TelephonyIcons
@@ -41,7 +45,7 @@ internal class MobileState(
@JvmField var roaming: Boolean = false,
@JvmField var dataState: Int = TelephonyManager.DATA_DISCONNECTED,
// Tracks the on/off state of the defaultDataSubscription
- @JvmField var defaultDataOff: Boolean = false
+ @JvmField var defaultDataOff: Boolean = false,
) : ConnectivityState() {
@JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
@@ -49,6 +53,11 @@ internal class MobileState(
@JvmField var serviceState: ServiceState? = null
@JvmField var signalStrength: SignalStrength? = null
+ var carrierId = TelephonyManager.UNKNOWN_CARRIER_ID
+
+ @VisibleForTesting
+ var networkTypeResIdCache: NetworkTypeResIdCache = NetworkTypeResIdCache()
+
/** @return true if this state is disabled or not default data */
val isDataDisabledOrNotDefault: Boolean
get() = (iconGroup === TelephonyIcons.DATA_DISABLED ||
@@ -125,6 +134,21 @@ internal class MobileState(
return serviceState != null && serviceState!!.roaming
}
+ /**
+ *
+ * Load the (potentially customized) icon resource id for the current network type. Note that
+ * this operation caches the result. Note that reading the [MobileIconGroup.dataType] field
+ * directly will not yield correct results in cases where the carrierId has an associated
+ * override. This is the preferred method for getting the network type indicator.
+ *
+ * @return a drawable res id appropriate for the current (carrierId, networkType) pair
+ */
+ @DrawableRes
+ fun getNetworkTypeIcon(context: Context): Int {
+ val icon = (iconGroup as MobileIconGroup)
+ return networkTypeResIdCache.get(icon, carrierId, context)
+ }
+
fun setFromMobileStatus(mobileStatus: MobileStatus) {
activityIn = mobileStatus.activityIn
activityOut = mobileStatus.activityOut
@@ -140,6 +164,7 @@ internal class MobileState(
super.toString(builder)
builder.append(',')
builder.append("dataSim=$dataSim,")
+ builder.append("carrierId=$carrierId")
builder.append("networkName=$networkName,")
builder.append("networkNameData=$networkNameData,")
builder.append("dataConnected=$dataConnected,")
@@ -157,6 +182,8 @@ internal class MobileState(
builder.append("voiceServiceState=${getVoiceServiceState()},")
builder.append("isInService=${isInService()},")
+ builder.append("networkTypeIconCache=$networkTypeResIdCache")
+
builder.append("serviceState=${serviceState?.minLog() ?: "(null)"},")
builder.append("signalStrength=${signalStrength?.minLog() ?: "(null)"},")
builder.append("displayInfo=$telephonyDisplayInfo")
@@ -164,6 +191,7 @@ internal class MobileState(
override fun tableColumns(): List<String> {
val columns = listOf("dataSim",
+ "carrierId",
"networkName",
"networkNameData",
"dataConnected",
@@ -178,6 +206,7 @@ internal class MobileState(
"showQuickSettingsRatIcon",
"voiceServiceState",
"isInService",
+ "networkTypeIconCache",
"serviceState",
"signalStrength",
"displayInfo")
@@ -187,6 +216,7 @@ internal class MobileState(
override fun tableData(): List<String> {
val columns = listOf(dataSim,
+ carrierId,
networkName,
networkNameData,
dataConnected,
@@ -201,6 +231,7 @@ internal class MobileState(
showQuickSettingsRatIcon(),
getVoiceServiceState(),
isInService(),
+ networkTypeResIdCache,
serviceState?.minLog() ?: "(null)",
signalStrength?.minLog() ?: "(null)",
telephonyDisplayInfo).map { it.toString() }
@@ -217,6 +248,7 @@ internal class MobileState(
if (networkName != other.networkName) return false
if (networkNameData != other.networkNameData) return false
+ if (carrierId != other.carrierId) return false
if (dataSim != other.dataSim) return false
if (dataConnected != other.dataConnected) return false
if (isEmergency != other.isEmergency) return false
@@ -238,6 +270,7 @@ internal class MobileState(
var result = super.hashCode()
result = 31 * result + (networkName?.hashCode() ?: 0)
result = 31 * result + (networkNameData?.hashCode() ?: 0)
+ result = 31 * result + (carrierId.hashCode())
result = 31 * result + dataSim.hashCode()
result = 31 * result + dataConnected.hashCode()
result = 31 * result + isEmergency.hashCode()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 450b757295bc..73d6483e65fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -22,6 +22,7 @@ import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -138,7 +139,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
private final MobileSignalControllerFactory mMobileFactory;
private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener;
- private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private int mActiveMobileDataSubscription = INVALID_SUBSCRIPTION_ID;
// Subcontrollers.
@VisibleForTesting
@@ -502,6 +503,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
+ filter.addAction(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED);
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
mListening = true;
@@ -792,6 +794,20 @@ public class NetworkControllerImpl extends BroadcastReceiver
mConfig = Config.readConfig(mContext);
mReceiverHandler.post(this::handleConfigurationChanged);
break;
+
+ case TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED: {
+ // Notify the relevant MobileSignalController of the change
+ int subId = intent.getIntExtra(
+ TelephonyManager.EXTRA_SUBSCRIPTION_ID,
+ INVALID_SUBSCRIPTION_ID
+ );
+ if (SubscriptionManager.isValidSubscriptionId(subId)) {
+ if (mMobileSignalControllers.indexOfKey(subId) >= 0) {
+ mMobileSignalControllers.get(subId).handleBroadcast(intent);
+ }
+ }
+ }
+ break;
case Intent.ACTION_SIM_STATE_CHANGED:
// Avoid rebroadcast because SysUI is direct boot aware.
if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
@@ -819,7 +835,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
break;
default:
int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
- SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ INVALID_SUBSCRIPTION_ID);
if (SubscriptionManager.isValidSubscriptionId(subId)) {
if (mMobileSignalControllers.indexOfKey(subId) >= 0) {
mMobileSignalControllers.get(subId).handleBroadcast(intent);
@@ -1335,6 +1351,9 @@ public class NetworkControllerImpl extends BroadcastReceiver
String slotString = args.getString("slot");
int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString);
slot = MathUtils.constrain(slot, 0, 8);
+ String carrierIdString = args.getString("carrierid");
+ int carrierId = TextUtils.isEmpty(carrierIdString) ? 0
+ : Integer.parseInt(carrierIdString);
// Ensure we have enough sim slots
List<SubscriptionInfo> subs = new ArrayList<>();
while (mMobileSignalControllers.size() <= slot) {
@@ -1346,6 +1365,9 @@ public class NetworkControllerImpl extends BroadcastReceiver
}
// Hack to index linearly for easy use.
MobileSignalController controller = mMobileSignalControllers.valueAt(slot);
+ if (carrierId != 0) {
+ controller.getState().setCarrierId(carrierId);
+ }
controller.getState().dataSim = datatype != null;
controller.getState().isDefault = datatype != null;
controller.getState().dataConnected = datatype != null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt
new file mode 100644
index 000000000000..9be7ee99cf02
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.connectivity
+
+import android.annotation.DrawableRes
+import android.content.Context
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
+import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
+
+/**
+ * Cache for network type resource IDs.
+ *
+ * The default framework behavior is to have a statically defined icon per network type. See
+ * [MobileIconGroup] for the standard mapping.
+ *
+ * For the case of carrierId-defined overrides, we want to check [MobileIconCarrierIdOverrides] for
+ * an existing icon override, and cache the result of the operation
+ */
+class NetworkTypeResIdCache(
+ private val overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
+) {
+ @DrawableRes private var cachedResId: Int = 0
+ private var lastCarrierId: Int? = null
+ private var lastIconGroup: MobileIconGroup? = null
+ private var isOverridden: Boolean = false
+
+ @DrawableRes
+ fun get(iconGroup: MobileIconGroup, carrierId: Int, context: Context): Int {
+ if (lastCarrierId != carrierId || lastIconGroup != iconGroup) {
+ lastCarrierId = carrierId
+ lastIconGroup = iconGroup
+
+ val maybeOverride = calculateOverriddenIcon(iconGroup, carrierId, context)
+ if (maybeOverride > 0) {
+ cachedResId = maybeOverride
+ isOverridden = true
+ } else {
+ cachedResId = iconGroup.dataType
+ isOverridden = false
+ }
+ }
+
+ return cachedResId
+ }
+
+ override fun toString(): String {
+ return "networkTypeResIdCache={id=$cachedResId, isOverridden=$isOverridden}"
+ }
+
+ @DrawableRes
+ private fun calculateOverriddenIcon(
+ iconGroup: MobileIconGroup,
+ carrierId: Int,
+ context: Context,
+ ): Int {
+ val name = iconGroup.name
+ if (!overrides.carrierIdEntryExists(carrierId)) {
+ return 0
+ }
+
+ return overrides.getOverrideFor(carrierId, name, context.resources)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt
index a02dd3490341..42b874fd7156 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt
@@ -37,6 +37,13 @@ import javax.inject.Inject
* own [Configuration] and track resources based on the full set of available mcc-mnc combinations.
*
* (for future reference: b/240555502 is the initiating bug for this)
+ *
+ * NOTE: MCC/MNC qualifiers are not sufficient to fully describe a network type icon qualified by
+ * network type + carrier ID. This class exists to keep the legacy behavior of using the MCC/MNC
+ * resource qualifiers working, but if a carrier-specific icon is requested, then the override
+ * provided by [MobileIconCarrierIdOverrides] will take precedence.
+ *
+ * TODO(b/258503704): consider removing this class in favor of the `carrierId` overrides
*/
@SysUISingleton
class MobileContextProvider
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 41f0520a9d14..f3482f490d92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -55,6 +55,7 @@ import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
@@ -71,6 +72,8 @@ import java.util.Optional;
import javax.inject.Inject;
+import dagger.Lazy;
+
/** */
@CentralSurfacesComponent.CentralSurfacesScope
public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callbacks {
@@ -99,6 +102,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
private final boolean mVibrateOnOpening;
private final VibrationEffect mCameraLaunchGestureVibrationEffect;
private final SystemBarAttributesListener mSystemBarAttributesListener;
+ private final Lazy<CameraLauncher> mCameraLauncherLazy;
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@@ -128,8 +132,8 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
Optional<Vibrator> vibratorOptional,
DisableFlagsLogger disableFlagsLogger,
@DisplayId int displayId,
- SystemBarAttributesListener systemBarAttributesListener) {
-
+ SystemBarAttributesListener systemBarAttributesListener,
+ Lazy<CameraLauncher> cameraLauncherLazy) {
mCentralSurfaces = centralSurfaces;
mContext = context;
mShadeController = shadeController;
@@ -152,6 +156,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
mVibratorOptional = vibratorOptional;
mDisableFlagsLogger = disableFlagsLogger;
mDisplayId = displayId;
+ mCameraLauncherLazy = cameraLauncherLazy;
mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
@@ -346,7 +351,8 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
mCentralSurfaces.setLaunchCameraOnFinishedGoingToSleep(true);
return;
}
- if (!mNotificationPanelViewController.canCameraGestureBeLaunched()) {
+ if (!mCameraLauncherLazy.get().canCameraGestureBeLaunched(
+ mNotificationPanelViewController.getBarState())) {
if (CentralSurfaces.DEBUG_CAMERA_LIFT) {
Slog.d(CentralSurfaces.TAG, "Can't launch camera right now");
}
@@ -383,7 +389,8 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.reset(true /* hide */);
}
- mNotificationPanelViewController.launchCamera(source);
+ mCameraLauncherLazy.get().launchCamera(source,
+ mNotificationPanelViewController.isFullyCollapsed());
mCentralSurfaces.updateScrimController();
} else {
// We need to defer the camera launch until the screen comes on, since otherwise
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 71609f887ebf..334f1aff0d7d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -175,6 +175,7 @@ import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.ripple.RippleShader.RippleShape;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -468,6 +469,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final PluginManager mPluginManager;
private final ShadeController mShadeController;
private final InitController mInitController;
+ private final Lazy<CameraLauncher> mCameraLauncherLazy;
private final PluginDependencyProvider mPluginDependencyProvider;
private final KeyguardDismissUtil mKeyguardDismissUtil;
@@ -600,6 +602,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private Runnable mLaunchTransitionEndRunnable;
private Runnable mLaunchTransitionCancelRunnable;
+ private boolean mLaunchingAffordance;
private boolean mLaunchCameraWhenFinishedWaking;
private boolean mLaunchCameraOnFinishedGoingToSleep;
private boolean mLaunchEmergencyActionWhenFinishedWaking;
@@ -744,7 +747,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
InteractionJankMonitor jankMonitor,
DeviceStateManager deviceStateManager,
WiredChargingRippleController wiredChargingRippleController,
- IDreamManager dreamManager) {
+ IDreamManager dreamManager,
+ Lazy<CameraLauncher> cameraLauncherLazy) {
mContext = context;
mNotificationsController = notificationsController;
mFragmentService = fragmentService;
@@ -821,6 +825,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mMessageRouter = messageRouter;
mWallpaperManager = wallpaperManager;
mJankMonitor = jankMonitor;
+ mCameraLauncherLazy = cameraLauncherLazy;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -2949,7 +2954,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private void onLaunchTransitionFadingEnded() {
mNotificationPanelViewController.resetAlpha();
- mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mCameraLauncherLazy.get().setLaunchingAffordance(false);
releaseGestureWakeLock();
runLaunchTransitionEndRunnable();
mKeyguardStateController.setLaunchTransitionFadingAway(false);
@@ -3019,7 +3024,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private void onLaunchTransitionTimeout() {
Log.w(TAG, "Launch transition: Timeout!");
- mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mCameraLauncherLazy.get().setLaunchingAffordance(false);
releaseGestureWakeLock();
mNotificationPanelViewController.resetViews(false /* animate */);
}
@@ -3072,7 +3077,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
releaseGestureWakeLock();
- mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mCameraLauncherLazy.get().setLaunchingAffordance(false);
mNotificationPanelViewController.resetAlpha();
mNotificationPanelViewController.resetTranslation();
mNotificationPanelViewController.resetViewGroupFade();
@@ -3230,7 +3235,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public void endAffordanceLaunch() {
releaseGestureWakeLock();
- mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mCameraLauncherLazy.get().setLaunchingAffordance(false);
}
/**
@@ -3503,7 +3508,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedGoingToSleep() {
- mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mCameraLauncherLazy.get().setLaunchingAffordance(false);
releaseGestureWakeLock();
mLaunchCameraWhenFinishedWaking = false;
mDeviceInteractive = false;
@@ -3604,7 +3609,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
.updateSensitivenessForOccludedWakeup();
}
if (mLaunchCameraWhenFinishedWaking) {
- mNotificationPanelViewController.launchCamera(mLastCameraLaunchSource);
+ mCameraLauncherLazy.get().launchCamera(mLastCameraLaunchSource,
+ mNotificationPanelViewController.isFullyCollapsed());
mLaunchCameraWhenFinishedWaking = false;
}
if (mLaunchEmergencyActionWhenFinishedWaking) {
@@ -3795,8 +3801,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mScrimController.setExpansionAffectsAlpha(!unlocking);
- boolean launchingAffordanceWithPreview =
- mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
+ boolean launchingAffordanceWithPreview = mLaunchingAffordance;
mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index d32847227c1a..aa0757e1d572 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -279,10 +279,7 @@ public class KeyguardBouncer {
* @see #onFullyShown()
*/
private void onFullyHidden() {
- cancelShowRunnable();
- setVisibility(View.INVISIBLE);
- mFalsingCollector.onBouncerHidden();
- DejankUtils.postAfterTraversal(mResetRunnable);
+
}
private void setVisibility(@View.Visibility int visibility) {
@@ -459,7 +456,13 @@ public class KeyguardBouncer {
onFullyShown();
dispatchFullyShown();
} else if (fraction == EXPANSION_HIDDEN && oldExpansion != EXPANSION_HIDDEN) {
- onFullyHidden();
+ DejankUtils.postAfterTraversal(mResetRunnable);
+ /*
+ * There are cases where #hide() was not invoked, such as when
+ * NotificationPanelViewController controls the hide animation. Make sure the state gets
+ * updated by calling #hide() directly.
+ */
+ hide(false /* destroyView */);
dispatchFullyHidden();
} else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) {
dispatchStartingToHide();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 86e27aba65f0..d54a8638f2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -895,7 +895,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
float stateBehind = mClipsQsScrim ? state.getNotifAlpha() : state.getBehindAlpha();
float behindAlpha;
- int behindTint;
+ int behindTint = state.getBehindTint();
if (mDarkenWhileDragging) {
behindAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind,
interpolatedFract);
@@ -903,12 +903,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
behindAlpha = MathUtils.lerp(0 /* start */, stateBehind,
interpolatedFract);
}
- if (mClipsQsScrim) {
- behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(),
+ if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) {
+ if (mClipsQsScrim) {
+ behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(),
state.getNotifTint(), interpolatedFract);
- } else {
- behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
+ } else {
+ behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
state.getBehindTint(), interpolatedFract);
+ }
}
if (mQsExpansion > 0) {
behindAlpha = MathUtils.lerp(behindAlpha, mDefaultScrimAlpha, mQsExpansion);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index e9d4e55c82f2..681d21bf078d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -165,6 +165,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Override
public void onFullyHidden() {
mPrimaryBouncerAnimating = false;
+ updateStates();
}
@Override
@@ -1183,12 +1184,16 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
updateNavigationBarVisibility(navBarVisible);
}
- if (primaryBouncerShowing != mLastPrimaryBouncerShowing || mFirstUpdate) {
+ boolean isPrimaryBouncerShowingChanged =
+ primaryBouncerShowing != mLastPrimaryBouncerShowing;
+ mLastPrimaryBouncerShowing = primaryBouncerShowing;
+
+ if (isPrimaryBouncerShowingChanged || mFirstUpdate) {
mNotificationShadeWindowController.setBouncerShowing(primaryBouncerShowing);
mCentralSurfaces.setBouncerShowing(primaryBouncerShowing);
}
if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing || mFirstUpdate
- || primaryBouncerShowing != mLastPrimaryBouncerShowing) {
+ || isPrimaryBouncerShowingChanged) {
mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
primaryBouncerShowing);
}
@@ -1197,7 +1202,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mLastShowing = showing;
mLastGlobalActionsVisible = mGlobalActionsVisible;
mLastOccluded = occluded;
- mLastPrimaryBouncerShowing = primaryBouncerShowing;
mLastPrimaryBouncerIsOrWillBeShowing = primaryBouncerIsOrWillBeShowing;
mLastBouncerDismissible = primaryBouncerDismissible;
mLastRemoteInputActive = remoteInputActive;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
index 60bd0383f8c7..501467f13007 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
@@ -32,6 +32,7 @@ import javax.inject.Inject
interface MobileMappingsProxy {
fun mapIconSets(config: Config): Map<String, MobileIconGroup>
fun getDefaultIcons(config: Config): MobileIconGroup
+ fun getIconKey(displayInfo: TelephonyDisplayInfo): String
fun toIconKey(@NetworkType networkType: Int): String
fun toIconKeyOverride(@NetworkType networkType: Int): String
}
@@ -44,6 +45,9 @@ class MobileMappingsProxyImpl @Inject constructor() : MobileMappingsProxy {
override fun getDefaultIcons(config: Config): MobileIconGroup =
MobileMappings.getDefaultIcons(config)
+ override fun getIconKey(displayInfo: TelephonyDisplayInfo): String =
+ MobileMappings.getIconKey(displayInfo)
+
override fun toIconKey(@NetworkType networkType: Int): String =
MobileMappings.toIconKey(networkType)
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 4cb41f3a977e..82703364a1d5 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -65,8 +65,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
height = WindowManager.LayoutParams.WRAP_CONTENT
type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
- WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
- WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
}
@@ -120,20 +119,27 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
// At this point, we're guaranteed to no longer be displaying a view.
// So, set up all our callbacks and inflate the view.
configurationController.addCallback(displayScaleListener)
- // Wake the screen if necessary so the user will see the view. (Per b/239426653, we want
- // the view to show over the dream state, so we should only wake up if the screen is
- // completely off.)
- if (!powerManager.isScreenOn) {
- wakeLock = wakeLockBuilder
+
+ wakeLock = if (!powerManager.isScreenOn) {
+ // If the screen is off, fully wake it so the user can see the view.
+ wakeLockBuilder
.setTag(newInfo.windowTitle)
.setLevelsAndFlags(
- PowerManager.FULL_WAKE_LOCK or
- PowerManager.ACQUIRE_CAUSES_WAKEUP
+ PowerManager.FULL_WAKE_LOCK or
+ PowerManager.ACQUIRE_CAUSES_WAKEUP
)
.build()
- wakeLock?.acquire(newInfo.wakeReason)
- wakeReasonAcquired = newInfo.wakeReason
+ } else {
+ // Per b/239426653, we want the view to show over the dream state.
+ // If the screen is on, using screen bright level will leave screen on the dream
+ // state but ensure the screen will not go off before wake lock is released.
+ wakeLockBuilder
+ .setTag(newInfo.windowTitle)
+ .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
+ .build()
}
+ wakeLock?.acquire(newInfo.wakeReason)
+ wakeReasonAcquired = newInfo.wakeReason
logger.logViewAddition(newInfo.windowTitle)
inflateAndUpdateView(newInfo)
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index bf706735d531..3507cb7c40a4 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -381,7 +381,7 @@ public class StorageNotification implements CoreStartable {
// Don't annoy when user dismissed in past. (But make sure the disk is adoptable; we
// used to allow snoozing non-adoptable disks too.)
- if (rec.isSnoozed() && disk.isAdoptable()) {
+ if (rec == null || (rec.isSnoozed() && disk.isAdoptable())) {
return null;
}
if (disk.isAdoptable() && !rec.isInited() && rec.getType() != VolumeInfo.TYPE_PUBLIC
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 903aba1a74a4..2c64fe1e259b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1230,6 +1230,9 @@ public class VolumeDialogImpl implements VolumeDialog,
effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
break;
case RINGER_MODE_VIBRATE:
+ // Feedback handled by onStateChange, for feedback both when user toggles
+ // directly in volume dialog, or drags slider to a value of 0 in settings.
+ break;
default:
effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
}
@@ -1630,9 +1633,8 @@ public class VolumeDialogImpl implements VolumeDialog,
&& mState.ringerModeInternal != -1
&& mState.ringerModeInternal != state.ringerModeInternal
&& state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
- mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK));
+ mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
}
-
mState = state;
mDynamic.clear();
// add any new dynamic rows
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 5d2b0ca4e7ea..829008403e02 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -16,8 +16,11 @@
package com.android.keyguard;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -90,4 +93,11 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase {
mMessageAreaController.setIsVisible(true);
verify(mKeyguardMessageArea).setIsVisible(true);
}
+
+ @Test
+ public void testGetMessage() {
+ String msg = "abc";
+ when(mKeyguardMessageArea.getText()).thenReturn(msg);
+ assertThat(mMessageAreaController.getMessage()).isEqualTo(msg);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index b369098cafc0..ffd95f4041f9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -31,6 +31,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -118,4 +119,14 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
keyguardPasswordViewController.startAppearAnimation()
verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
}
+
+ @Test
+ fun startAppearAnimation_withExistingMessage() {
+ `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ keyguardPasswordViewController.startAppearAnimation()
+ verify(
+ mKeyguardMessageAreaController,
+ never()
+ ).setMessage(R.string.keyguard_enter_your_password)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 9eff70487c74..b3d1c8f909d8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -33,6 +33,7 @@ import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
import org.mockito.MockitoAnnotations
@SmallTest
@@ -112,4 +113,14 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
mKeyguardPatternViewController.startAppearAnimation()
verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
}
+
+ @Test
+ fun startAppearAnimation_withExistingMessage() {
+ `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ mKeyguardPatternViewController.startAppearAnimation()
+ verify(
+ mKeyguardMessageAreaController,
+ never()
+ ).setMessage(R.string.keyguard_enter_your_password)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index d9efdeaea04c..8bcfe6f2b6f5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -100,4 +100,12 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
pinViewController.startAppearAnimation()
verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin)
}
+
+ @Test
+ fun startAppearAnimation_withExistingMessage() {
+ Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ pinViewController.startAppearAnimation()
+ verify(keyguardMessageAreaController, Mockito.never())
+ .setMessage(R.string.keyguard_enter_your_password)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 1bd14e558fa0..1d2b09ce0ce2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -262,9 +262,12 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
ConstraintSet.Constraint userSwitcherConstraint =
getViewConstraint(R.id.keyguard_bouncer_user_switcher);
- assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.topToBottom).isEqualTo(
+ R.id.keyguard_bouncer_user_switcher);
assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.bottomToTop).isEqualTo(
+ mSecurityViewFlipper.getId());
assertThat(userSwitcherConstraint.layout.topMargin).isEqualTo(
getContext().getResources().getDimensionPixelSize(
R.dimen.bouncer_user_switcher_y_trans));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt
index a4e0825360df..588620646b73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui
+import android.content.Context
import android.graphics.Canvas
import android.graphics.Insets
import android.graphics.Path
@@ -48,6 +49,7 @@ class DisplayCutoutBaseViewTest : SysuiTestCase() {
@Mock private lateinit var mockCanvas: Canvas
@Mock private lateinit var mockRootView: View
@Mock private lateinit var mockDisplay: Display
+ @Mock private lateinit var mockContext: Context
private lateinit var cutoutBaseView: DisplayCutoutBaseView
private val cutout: DisplayCutout = DisplayCutout.Builder()
@@ -168,7 +170,9 @@ class DisplayCutoutBaseViewTest : SysuiTestCase() {
R.bool.config_fillMainBuiltInDisplayCutout, fillCutout)
cutoutBaseView = spy(DisplayCutoutBaseView(mContext))
- whenever(cutoutBaseView.display).thenReturn(mockDisplay)
+
+ whenever(cutoutBaseView.context).thenReturn(mockContext)
+ whenever(mockContext.display).thenReturn(mockDisplay)
whenever(mockDisplay.uniqueId).thenReturn("mockDisplayUniqueId")
whenever(cutoutBaseView.rootView).thenReturn(mockRootView)
whenever(cutoutBaseView.getPhysicalPixelDisplaySizeRatio()).thenReturn(1f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
index 054650bb8a75..8207fa6958f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui
+import android.content.Context
import android.graphics.Insets
import android.graphics.PixelFormat
import android.graphics.Rect
@@ -44,6 +45,7 @@ class ScreenDecorHwcLayerTest : SysuiTestCase() {
@Mock private lateinit var mockDisplay: Display
@Mock private lateinit var mockRootView: View
+ @Mock private lateinit var mockContext: Context
private val displayWidth = 100
private val displayHeight = 200
@@ -75,7 +77,8 @@ class ScreenDecorHwcLayerTest : SysuiTestCase() {
decorHwcLayer = Mockito.spy(ScreenDecorHwcLayer(mContext, decorationSupport))
whenever(decorHwcLayer.width).thenReturn(displayWidth)
whenever(decorHwcLayer.height).thenReturn(displayHeight)
- whenever(decorHwcLayer.display).thenReturn(mockDisplay)
+ whenever(decorHwcLayer.context).thenReturn(mockContext)
+ whenever(mockContext.display).thenReturn(mockDisplay)
whenever(decorHwcLayer.rootView).thenReturn(mockRootView)
whenever(mockRootView.left).thenReturn(0)
whenever(mockRootView.top).thenReturn(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 1b5f9b6d45cd..acdafe3e1c7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -114,10 +114,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
-
// Unit under test
private UdfpsController mUdfpsController;
-
// Dependencies
private FakeExecutor mBiometricsExecutor;
@Mock
@@ -171,7 +169,6 @@ public class UdfpsControllerTest extends SysuiTestCase {
private UdfpsDisplayMode mUdfpsDisplayMode;
@Mock
private FeatureFlags mFeatureFlags;
-
// Stuff for configuring mocks
@Mock
private UdfpsView mUdfpsView;
@@ -249,54 +246,42 @@ public class UdfpsControllerTest extends SysuiTestCase {
FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
true /* resetLockoutRequiresHardwareAuthToken */);
- List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
- props.add(mOpticalProps);
- props.add(mUltrasonicProps);
- when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
-
mFgExecutor = new FakeExecutor(new FakeSystemClock());
// Create a fake background executor.
mBiometricsExecutor = new FakeExecutor(new FakeSystemClock());
- mUdfpsController = new UdfpsController(
- mContext,
- execution,
- mLayoutInflater,
- mFingerprintManager,
- mWindowManager,
- mStatusBarStateController,
- mFgExecutor,
- new ShadeExpansionStateManager(),
- mStatusBarKeyguardViewManager,
- mDumpManager,
- mKeyguardUpdateMonitor,
- mFeatureFlags,
- mFalsingManager,
- mPowerManager,
- mAccessibilityManager,
- mLockscreenShadeTransitionController,
- mScreenLifecycle,
- mVibrator,
- mUdfpsHapticsSimulator,
- mUdfpsShell,
- mKeyguardStateController,
- mDisplayManager,
- mHandler,
- mConfigurationController,
- mSystemClock,
- mUnlockedScreenOffAnimationController,
- mSystemUIDialogManager,
- mLatencyTracker,
- mActivityLaunchAnimator,
- Optional.of(mAlternateTouchProvider),
- mBiometricsExecutor,
+ initUdfpsController(true /* hasAlternateTouchProvider */);
+ }
+
+ private void initUdfpsController(boolean hasAlternateTouchProvider) {
+ initUdfpsController(mOpticalProps, hasAlternateTouchProvider);
+ }
+
+ private void initUdfpsController(FingerprintSensorPropertiesInternal sensorProps,
+ boolean hasAlternateTouchProvider) {
+ reset(mFingerprintManager);
+ reset(mScreenLifecycle);
+
+ final Optional<AlternateUdfpsTouchProvider> alternateTouchProvider =
+ hasAlternateTouchProvider ? Optional.of(mAlternateTouchProvider) : Optional.empty();
+
+ mUdfpsController = new UdfpsController(mContext, new FakeExecution(), mLayoutInflater,
+ mFingerprintManager, mWindowManager, mStatusBarStateController, mFgExecutor,
+ new ShadeExpansionStateManager(), mStatusBarKeyguardViewManager, mDumpManager,
+ mKeyguardUpdateMonitor, mFeatureFlags, mFalsingManager, mPowerManager,
+ mAccessibilityManager, mLockscreenShadeTransitionController, mScreenLifecycle,
+ mVibrator, mUdfpsHapticsSimulator, mUdfpsShell, mKeyguardStateController,
+ mDisplayManager, mHandler, mConfigurationController, mSystemClock,
+ mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
+ mActivityLaunchAnimator, alternateTouchProvider, mBiometricsExecutor,
mPrimaryBouncerInteractor);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
mScreenObserver = mScreenObserverCaptor.getValue();
- mUdfpsController.updateOverlayParams(mOpticalProps, new UdfpsOverlayParams());
+
+ mUdfpsController.updateOverlayParams(sensorProps, new UdfpsOverlayParams());
mUdfpsController.setUdfpsDisplayMode(mUdfpsDisplayMode);
}
@@ -333,8 +318,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
}
@Test
- public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice()
- throws RemoteException {
+ public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice() throws RemoteException {
onActionMoveTouch_whenCanDismissLockScreen_entersDevice(false /* stale */);
}
@@ -521,8 +505,37 @@ public class UdfpsControllerTest extends SysuiTestCase {
new MotionEvent.PointerCoords[]{pc}, 0, 0, 1f, 1f, 0, 0, 0, 0);
}
+ private static class TestParams {
+ public final FingerprintSensorPropertiesInternal sensorProps;
+ public final boolean hasAlternateTouchProvider;
+
+ TestParams(FingerprintSensorPropertiesInternal sensorProps,
+ boolean hasAlternateTouchProvider) {
+ this.sensorProps = sensorProps;
+ this.hasAlternateTouchProvider = hasAlternateTouchProvider;
+ }
+ }
+
+ private void runWithAllParams(ThrowingConsumer<TestParams> testParamsConsumer) {
+ for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps,
+ mUltrasonicProps)) {
+ for (boolean hasAlternateTouchProvider : new boolean[]{false, true}) {
+ initUdfpsController(sensorProps, hasAlternateTouchProvider);
+ testParamsConsumer.accept(new TestParams(sensorProps, hasAlternateTouchProvider));
+ }
+ }
+ }
+
@Test
- public void onTouch_propagatesTouchInNativeOrientationAndResolution() throws RemoteException {
+ public void onTouch_propagatesTouchInNativeOrientationAndResolution() {
+ runWithAllParams(
+ this::onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized);
+ }
+
+ private void onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized(
+ TestParams testParams) throws RemoteException {
+ reset(mUdfpsView);
+
final Rect sensorBounds = new Rect(1000, 1900, 1080, 1920); // Bottom right corner.
final int displayWidth = 1080;
final int displayHeight = 1920;
@@ -541,13 +554,13 @@ public class UdfpsControllerTest extends SysuiTestCase {
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
// Show the overlay.
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
// Test ROTATION_0
- mUdfpsController.updateOverlayParams(mOpticalProps,
+ mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_0));
MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor,
@@ -559,12 +572,19 @@ public class UdfpsControllerTest extends SysuiTestCase {
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricsExecutor.runAllReady();
event.recycle();
- verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
- eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+ if (testParams.hasAlternateTouchProvider) {
+ verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
+ eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+ } else {
+ verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+ eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
+ eq(expectedMinor), eq(expectedMajor));
+ }
// Test ROTATION_90
reset(mAlternateTouchProvider);
- mUdfpsController.updateOverlayParams(mOpticalProps,
+ reset(mFingerprintManager);
+ mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_90));
event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor);
@@ -575,12 +595,19 @@ public class UdfpsControllerTest extends SysuiTestCase {
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricsExecutor.runAllReady();
event.recycle();
- verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
- eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+ if (testParams.hasAlternateTouchProvider) {
+ verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
+ eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+ } else {
+ verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+ eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
+ eq(expectedMinor), eq(expectedMajor));
+ }
// Test ROTATION_270
reset(mAlternateTouchProvider);
- mUdfpsController.updateOverlayParams(mOpticalProps,
+ reset(mFingerprintManager);
+ mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_270));
event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor);
@@ -591,12 +618,19 @@ public class UdfpsControllerTest extends SysuiTestCase {
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricsExecutor.runAllReady();
event.recycle();
- verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
- eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+ if (testParams.hasAlternateTouchProvider) {
+ verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
+ eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+ } else {
+ verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+ eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
+ eq(expectedMinor), eq(expectedMajor));
+ }
// Test ROTATION_180
reset(mAlternateTouchProvider);
- mUdfpsController.updateOverlayParams(mOpticalProps,
+ reset(mFingerprintManager);
+ mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_180));
// ROTATION_180 is not supported. It should be treated like ROTATION_0.
@@ -608,26 +642,22 @@ public class UdfpsControllerTest extends SysuiTestCase {
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricsExecutor.runAllReady();
event.recycle();
- verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
- eq(expectedY), eq(expectedMinor), eq(expectedMajor));
- }
-
- private void runForAllUdfpsTypes(
- ThrowingConsumer<FingerprintSensorPropertiesInternal> sensorPropsConsumer) {
- for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps,
- mUltrasonicProps)) {
- mUdfpsController.updateOverlayParams(sensorProps, new UdfpsOverlayParams());
- sensorPropsConsumer.accept(sensorProps);
+ if (testParams.hasAlternateTouchProvider) {
+ verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX),
+ eq(expectedY), eq(expectedMinor), eq(expectedMajor));
+ } else {
+ verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+ eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY),
+ eq(expectedMinor), eq(expectedMajor));
}
}
@Test
public void fingerDown() {
- runForAllUdfpsTypes(this::fingerDownForSensor);
+ runWithAllParams(this::fingerDownParameterized);
}
- private void fingerDownForSensor(FingerprintSensorPropertiesInternal sensorProps)
- throws RemoteException {
+ private void fingerDownParameterized(TestParams testParams) throws RemoteException {
reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mLatencyTracker,
mKeyguardUpdateMonitor);
@@ -637,7 +667,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// GIVEN that the overlay is showing
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
@@ -655,14 +685,22 @@ public class UdfpsControllerTest extends SysuiTestCase {
mFgExecutor.runAllReady();
- // THEN FingerprintManager is notified about onPointerDown
- verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f),
- eq(0f));
- verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
- anyFloat(), anyFloat());
+ // THEN the touch provider is notified about onPointerDown.
+ if (testParams.hasAlternateTouchProvider) {
+ verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f),
+ eq(0f));
+ verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
+ anyInt(), anyFloat(), anyFloat());
+ verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
+ } else {
+ verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+ eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(0f), eq(0f));
+ verify(mAlternateTouchProvider, never()).onPointerDown(anyInt(), anyInt(), anyInt(),
+ anyFloat(), anyFloat());
+ }
// AND display configuration begins
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
} else {
@@ -671,16 +709,27 @@ public class UdfpsControllerTest extends SysuiTestCase {
verify(mUdfpsView, never()).configureDisplay(any());
}
verify(mLatencyTracker, never()).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
- verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// AND onDisplayConfigured notifies FingerprintManager about onUiReady
mOnDisplayConfiguredCaptor.getValue().run();
mBiometricsExecutor.runAllReady();
- InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
- inOrder.verify(mAlternateTouchProvider).onUiReady();
- inOrder.verify(mLatencyTracker).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+ if (testParams.hasAlternateTouchProvider) {
+ InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
+ inOrder.verify(mAlternateTouchProvider).onUiReady();
+ inOrder.verify(mLatencyTracker).onActionEnd(
+ eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+ verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt());
+ } else {
+ InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker);
+ inOrder.verify(mFingerprintManager).onUiReady(eq(TEST_REQUEST_ID),
+ eq(testParams.sensorProps.sensorId));
+ inOrder.verify(mLatencyTracker).onActionEnd(
+ eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+ verify(mAlternateTouchProvider, never()).onUiReady();
+ }
} else {
+ verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt());
verify(mAlternateTouchProvider, never()).onUiReady();
verify(mLatencyTracker, never()).onActionEnd(
eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
@@ -689,24 +738,23 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void aodInterrupt() {
- runForAllUdfpsTypes(this::aodInterruptForSensor);
+ runWithAllParams(this::aodInterruptParameterized);
}
- private void aodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps)
- throws RemoteException {
+ private void aodInterruptParameterized(TestParams testParams) throws RemoteException {
mUdfpsController.cancelAodInterrupt();
reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mKeyguardUpdateMonitor);
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// GIVEN that the overlay is showing and screen is on and fp is running
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
// WHEN fingerprint is requested because of AOD interrupt
mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// THEN display configuration begins
// AND onDisplayConfigured notifies FingerprintManager about onUiReady
verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
@@ -715,29 +763,37 @@ public class UdfpsControllerTest extends SysuiTestCase {
verify(mUdfpsView, never()).configureDisplay(mOnDisplayConfiguredCaptor.capture());
}
mBiometricsExecutor.runAllReady();
- verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID),
- eq(0), eq(0), eq(3f) /* minor */, eq(2f) /* major */);
- verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
- anyFloat(), anyFloat());
- verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
+
+ if (testParams.hasAlternateTouchProvider) {
+ verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0),
+ eq(3f) /* minor */, eq(2f) /* major */);
+ verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
+ anyInt(), anyFloat(), anyFloat());
+ verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
+ } else {
+ verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID),
+ eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(3f) /* minor */,
+ eq(2f) /* major */);
+ verify(mAlternateTouchProvider, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
+ anyFloat(), anyFloat());
+ }
}
@Test
public void cancelAodInterrupt() {
- runForAllUdfpsTypes(this::cancelAodInterruptForSensor);
+ runWithAllParams(this::cancelAodInterruptParameterized);
}
- private void cancelAodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps)
- throws RemoteException {
+ private void cancelAodInterruptParameterized(TestParams testParams) throws RemoteException {
reset(mUdfpsView);
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
// WHEN it is cancelled
mUdfpsController.cancelAodInterrupt();
@@ -754,21 +810,20 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void aodInterruptTimeout() {
- runForAllUdfpsTypes(this::aodInterruptTimeoutForSensor);
+ runWithAllParams(this::aodInterruptTimeoutParameterized);
}
- private void aodInterruptTimeoutForSensor(FingerprintSensorPropertiesInternal sensorProps)
- throws RemoteException {
+ private void aodInterruptTimeoutParameterized(TestParams testParams) throws RemoteException {
reset(mUdfpsView);
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
} else {
when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
@@ -776,7 +831,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// WHEN it times out
mFgExecutor.advanceClockToNext();
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// THEN the display is unconfigured.
verify(mUdfpsView).unconfigureDisplay();
} else {
@@ -787,23 +842,23 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void aodInterruptCancelTimeoutActionOnFingerUp() {
- runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnFingerUpForSensor);
+ runWithAllParams(this::aodInterruptCancelTimeoutActionOnFingerUpParameterized);
}
- private void aodInterruptCancelTimeoutActionOnFingerUpForSensor(
- FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+ private void aodInterruptCancelTimeoutActionOnFingerUpParameterized(TestParams testParams)
+ throws RemoteException {
reset(mUdfpsView);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// Configure UdfpsView to accept the ACTION_UP event
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
} else {
@@ -833,7 +888,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
moveEvent.recycle();
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// Configure UdfpsView to accept the finger up event
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
} else {
@@ -844,7 +899,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
mFgExecutor.advanceClockToNext();
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// THEN the display should be unconfigured once. If the timeout action is not
// cancelled, the display would be unconfigured twice which would cause two
// FP attempts.
@@ -856,23 +911,23 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void aodInterruptCancelTimeoutActionOnAcquired() {
- runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnAcquiredForSensor);
+ runWithAllParams(this::aodInterruptCancelTimeoutActionOnAcquiredParameterized);
}
- private void aodInterruptCancelTimeoutActionOnAcquiredForSensor(
- FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+ private void aodInterruptCancelTimeoutActionOnAcquiredParameterized(TestParams testParams)
+ throws RemoteException {
reset(mUdfpsView);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// Configure UdfpsView to accept the acquired event
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
} else {
@@ -880,7 +935,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
}
// WHEN acquired is received
- mOverlayController.onAcquired(sensorProps.sensorId,
+ mOverlayController.onAcquired(testParams.sensorProps.sensorId,
BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD);
// Configure UdfpsView to accept the ACTION_DOWN event
@@ -900,7 +955,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
moveEvent.recycle();
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// Configure UdfpsView to accept the finger up event
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
} else {
@@ -911,7 +966,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
mFgExecutor.advanceClockToNext();
mFgExecutor.runAllReady();
- if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
// THEN the display should be unconfigured once. If the timeout action is not
// cancelled, the display would be unconfigured twice which would cause two
// FP attempts.
@@ -923,15 +978,14 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void aodInterruptScreenOff() {
- runForAllUdfpsTypes(this::aodInterruptScreenOffForSensor);
+ runWithAllParams(this::aodInterruptScreenOffParameterized);
}
- private void aodInterruptScreenOffForSensor(FingerprintSensorPropertiesInternal sensorProps)
- throws RemoteException {
+ private void aodInterruptScreenOffParameterized(TestParams testParams) throws RemoteException {
reset(mUdfpsView);
// GIVEN screen off
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOff();
mFgExecutor.runAllReady();
@@ -945,17 +999,16 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void aodInterrupt_fingerprintNotRunning() {
- runForAllUdfpsTypes(this::aodInterrupt_fingerprintNotRunningForSensor);
+ runWithAllParams(this::aodInterrupt_fingerprintNotRunningParameterized);
}
- private void aodInterrupt_fingerprintNotRunningForSensor(
- FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+ private void aodInterrupt_fingerprintNotRunningParameterized(TestParams testParams)
+ throws RemoteException {
reset(mUdfpsView);
// GIVEN showing overlay
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
- BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
- mUdfpsOverlayControllerCallback);
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index db41d8d37a43..dedc7239bf85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -16,27 +16,42 @@
package com.android.systemui.controls.management
+import android.Manifest
import android.content.ComponentName
import android.content.Context
import android.content.ContextWrapper
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
import android.content.pm.ServiceInfo
+import android.os.Bundle
import android.os.UserHandle
+import android.service.controls.ControlsProviderService
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.settingslib.applications.ServiceListing
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.USE_APP_PANELS
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import org.junit.After
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatcher
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
@@ -51,10 +66,8 @@ import java.util.concurrent.Executor
class ControlsListingControllerImplTest : SysuiTestCase() {
companion object {
- private const val TEST_LABEL = "TEST_LABEL"
- private const val TEST_PERMISSION = "permission"
- fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
- fun <T> any(): T = Mockito.any<T>()
+ private const val FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
}
@Mock
@@ -63,15 +76,17 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
@Mock
private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
- @Mock
- private lateinit var serviceInfo: ServiceInfo
- @Mock
- private lateinit var serviceInfo2: ServiceInfo
@Mock(stubOnly = true)
private lateinit var userTracker: UserTracker
+ @Mock(stubOnly = true)
+ private lateinit var dumpManager: DumpManager
+ @Mock
+ private lateinit var packageManager: PackageManager
+ @Mock
+ private lateinit var featureFlags: FeatureFlags
- private var componentName = ComponentName("pkg1", "class1")
- private var componentName2 = ComponentName("pkg2", "class2")
+ private var componentName = ComponentName("pkg", "class1")
+ private var activityName = ComponentName("pkg", "activity")
private val executor = FakeExecutor(FakeSystemClock())
@@ -87,9 +102,15 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(serviceInfo.componentName).thenReturn(componentName)
- `when`(serviceInfo2.componentName).thenReturn(componentName2)
`when`(userTracker.userId).thenReturn(user)
+ `when`(userTracker.userContext).thenReturn(context)
+ // Return disabled by default
+ `when`(packageManager.getComponentEnabledSetting(any()))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
+ mContext.setMockPackageManager(packageManager)
+
+ // Return true by default, we'll test the false path
+ `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(true)
val wrapper = object : ContextWrapper(mContext) {
override fun createContextAsUser(user: UserHandle, flags: Int): Context {
@@ -97,7 +118,14 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
}
}
- controller = ControlsListingControllerImpl(wrapper, executor, { mockSL }, userTracker)
+ controller = ControlsListingControllerImpl(
+ wrapper,
+ executor,
+ { mockSL },
+ userTracker,
+ dumpManager,
+ featureFlags
+ )
verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
}
@@ -123,9 +151,16 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
Unit
}
`when`(mockServiceListing.reload()).then {
- callback?.onServicesReloaded(listOf(serviceInfo))
+ callback?.onServicesReloaded(listOf(ServiceInfo(componentName)))
}
- ControlsListingControllerImpl(mContext, exec, { mockServiceListing }, userTracker)
+ ControlsListingControllerImpl(
+ mContext,
+ exec,
+ { mockServiceListing },
+ userTracker,
+ dumpManager,
+ featureFlags
+ )
}
@Test
@@ -148,7 +183,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
@Test
fun testCallbackGetsList() {
- val list = listOf(serviceInfo)
+ val list = listOf(ServiceInfo(componentName))
controller.addCallback(mockCallback)
controller.addCallback(mockCallbackOther)
@@ -188,6 +223,8 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
@Test
fun testChangeUserSendsCorrectServiceUpdate() {
+ val serviceInfo = ServiceInfo(componentName)
+
val list = listOf(serviceInfo)
controller.addCallback(mockCallback)
@@ -223,4 +260,284 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
verify(mockCallback).onServicesUpdated(capture(captor))
assertEquals(0, captor.value.size)
}
+
+ @Test
+ fun test_nullPanelActivity() {
+ val list = listOf(ServiceInfo(componentName))
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testNoActivity_nullPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityWithoutPermission_nullPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ setUpQueryResult(listOf(ActivityInfo(activityName)))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityPermissionNotExported_nullPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ setUpQueryResult(listOf(
+ ActivityInfo(activityName, permission = Manifest.permission.BIND_CONTROLS)
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityDisabled_nullPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityEnabled_correctPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertEquals(activityName, controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityDefaultEnabled_correctPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ enabled = true,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertEquals(activityName, controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityDefaultDisabled_nullPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ enabled = false,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityDefaultEnabled_flagDisabled_nullPanel() {
+ `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(false)
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName,
+ )
+
+ `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ enabled = true,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testActivityDifferentPackage_nullPanel() {
+ val serviceInfo = ServiceInfo(
+ componentName,
+ ComponentName("other_package", "cls")
+ )
+
+ `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ enabled = true,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ private fun ServiceInfo(
+ componentName: ComponentName,
+ panelActivityComponentName: ComponentName? = null
+ ): ServiceInfo {
+ return ServiceInfo().apply {
+ packageName = componentName.packageName
+ name = componentName.className
+ panelActivityComponentName?.let {
+ metaData = Bundle().apply {
+ putString(
+ ControlsProviderService.META_DATA_PANEL_ACTIVITY,
+ it.flattenToShortString()
+ )
+ }
+ }
+ }
+ }
+
+ private fun ActivityInfo(
+ componentName: ComponentName,
+ exported: Boolean = false,
+ enabled: Boolean = true,
+ permission: String? = null
+ ): ActivityInfo {
+ return ActivityInfo().apply {
+ packageName = componentName.packageName
+ name = componentName.className
+ this.permission = permission
+ this.exported = exported
+ this.enabled = enabled
+ }
+ }
+
+ private fun setUpQueryResult(infos: List<ActivityInfo>) {
+ `when`(
+ packageManager.queryIntentActivitiesAsUser(
+ argThat(IntentMatcher(activityName)),
+ argThat(FlagsMatcher(FLAGS)),
+ eq(UserHandle.of(user))
+ )
+ ).thenReturn(infos.map {
+ ResolveInfo().apply { activityInfo = it }
+ })
+ }
+
+ private class IntentMatcher(
+ private val componentName: ComponentName
+ ) : ArgumentMatcher<Intent> {
+ override fun matches(argument: Intent?): Boolean {
+ return argument?.component == componentName
+ }
+ }
+
+ private class FlagsMatcher(
+ private val flags: Long
+ ) : ArgumentMatcher<PackageManager.ResolveInfoFlags> {
+ override fun matches(argument: PackageManager.ResolveInfoFlags?): Boolean {
+ return flags == argument?.value
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
deleted file mode 100644
index 4d66a168303c..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Copyright (C) 2022 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.keyguard
-
-import android.content.pm.PackageManager
-import android.content.pm.ProviderInfo
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.SystemUIAppComponentFactoryBase
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderClient as Client
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
-
- @Mock private lateinit var lockPatternUtils: LockPatternUtils
- @Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var activityStarter: ActivityStarter
-
- private lateinit var underTest: KeyguardQuickAffordanceProvider
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- underTest = KeyguardQuickAffordanceProvider()
- val quickAffordanceRepository =
- KeyguardQuickAffordanceRepository(
- scope = CoroutineScope(IMMEDIATE),
- backgroundDispatcher = IMMEDIATE,
- selectionManager = KeyguardQuickAffordanceSelectionManager(),
- configs =
- setOf(
- FakeKeyguardQuickAffordanceConfig(
- key = AFFORDANCE_1,
- pickerIconResourceId = 1,
- ),
- FakeKeyguardQuickAffordanceConfig(
- key = AFFORDANCE_2,
- pickerIconResourceId = 2,
- ),
- ),
- )
- underTest.interactor =
- KeyguardQuickAffordanceInteractor(
- keyguardInteractor =
- KeyguardInteractor(
- repository = FakeKeyguardRepository(),
- ),
- registry = mock(),
- lockPatternUtils = lockPatternUtils,
- keyguardStateController = keyguardStateController,
- userTracker = userTracker,
- activityStarter = activityStarter,
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
- },
- repository = { quickAffordanceRepository },
- )
-
- underTest.attachInfoForTesting(
- context,
- ProviderInfo().apply { authority = Contract.AUTHORITY },
- )
- context.contentResolver.addProvider(Contract.AUTHORITY, underTest)
- context.testablePermissions.setPermission(
- Contract.PERMISSION,
- PackageManager.PERMISSION_GRANTED,
- )
- }
-
- @Test
- fun `onAttachInfo - reportsContext`() {
- val callback: SystemUIAppComponentFactoryBase.ContextAvailableCallback = mock()
- underTest.setContextAvailableCallback(callback)
-
- underTest.attachInfo(context, null)
-
- verify(callback).onContextAvailable(context)
- }
-
- @Test
- fun getType() {
- assertThat(underTest.getType(Contract.AffordanceTable.URI))
- .isEqualTo(
- "vnd.android.cursor.dir/vnd." +
- "${Contract.AUTHORITY}.${Contract.AffordanceTable.TABLE_NAME}"
- )
- assertThat(underTest.getType(Contract.SlotTable.URI))
- .isEqualTo(
- "vnd.android.cursor.dir/vnd.${Contract.AUTHORITY}.${Contract.SlotTable.TABLE_NAME}"
- )
- assertThat(underTest.getType(Contract.SelectionTable.URI))
- .isEqualTo(
- "vnd.android.cursor.dir/vnd." +
- "${Contract.AUTHORITY}.${Contract.SelectionTable.TABLE_NAME}"
- )
- }
-
- @Test
- fun `insert and query selection`() =
- runBlocking(IMMEDIATE) {
- val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
- val affordanceId = AFFORDANCE_2
-
- Client.insertSelection(
- context = context,
- slotId = slotId,
- affordanceId = affordanceId,
- dispatcher = IMMEDIATE,
- )
-
- assertThat(
- Client.querySelections(
- context = context,
- dispatcher = IMMEDIATE,
- )
- )
- .isEqualTo(
- listOf(
- Client.Selection(
- slotId = slotId,
- affordanceId = affordanceId,
- )
- )
- )
- }
-
- @Test
- fun `query slots`() =
- runBlocking(IMMEDIATE) {
- assertThat(
- Client.querySlots(
- context = context,
- dispatcher = IMMEDIATE,
- )
- )
- .isEqualTo(
- listOf(
- Client.Slot(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- capacity = 1,
- ),
- Client.Slot(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- capacity = 1,
- ),
- )
- )
- }
-
- @Test
- fun `query affordances`() =
- runBlocking(IMMEDIATE) {
- assertThat(
- Client.queryAffordances(
- context = context,
- dispatcher = IMMEDIATE,
- )
- )
- .isEqualTo(
- listOf(
- Client.Affordance(
- id = AFFORDANCE_1,
- name = AFFORDANCE_1,
- iconResourceId = 1,
- ),
- Client.Affordance(
- id = AFFORDANCE_2,
- name = AFFORDANCE_2,
- iconResourceId = 2,
- ),
- )
- )
- }
-
- @Test
- fun `delete and query selection`() =
- runBlocking(IMMEDIATE) {
- Client.insertSelection(
- context = context,
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- affordanceId = AFFORDANCE_1,
- dispatcher = IMMEDIATE,
- )
- Client.insertSelection(
- context = context,
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- affordanceId = AFFORDANCE_2,
- dispatcher = IMMEDIATE,
- )
-
- Client.deleteSelection(
- context = context,
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- affordanceId = AFFORDANCE_2,
- dispatcher = IMMEDIATE,
- )
-
- assertThat(
- Client.querySelections(
- context = context,
- dispatcher = IMMEDIATE,
- )
- )
- .isEqualTo(
- listOf(
- Client.Selection(
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- affordanceId = AFFORDANCE_1,
- )
- )
- )
- }
-
- @Test
- fun `delete all selections in a slot`() =
- runBlocking(IMMEDIATE) {
- Client.insertSelection(
- context = context,
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- affordanceId = AFFORDANCE_1,
- dispatcher = IMMEDIATE,
- )
- Client.insertSelection(
- context = context,
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- affordanceId = AFFORDANCE_2,
- dispatcher = IMMEDIATE,
- )
-
- Client.deleteAllSelections(
- context = context,
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- dispatcher = IMMEDIATE,
- )
-
- assertThat(
- Client.querySelections(
- context = context,
- dispatcher = IMMEDIATE,
- )
- )
- .isEqualTo(
- listOf(
- Client.Selection(
- slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- affordanceId = AFFORDANCE_1,
- )
- )
- )
- }
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- private const val AFFORDANCE_1 = "affordance_1"
- private const val AFFORDANCE_2 = "affordance_2"
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index ade83cf58e6e..6ba06344314c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Position
import com.android.systemui.doze.DozeHost
@@ -60,12 +61,12 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
underTest =
KeyguardRepositoryImpl(
- statusBarStateController,
- dozeHost,
- wakefulnessLifecycle,
- biometricUnlockController,
- keyguardStateController,
- keyguardUpdateMonitor,
+ statusBarStateController,
+ dozeHost,
+ wakefulnessLifecycle,
+ biometricUnlockController,
+ keyguardStateController,
+ keyguardUpdateMonitor,
)
}
@@ -257,6 +258,48 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun isKeyguardGoingAway() = runBlockingTest {
+ whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
+
+ whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
+ captor.value.onKeyguardGoingAwayChanged()
+ assertThat(latest).isTrue()
+
+ whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
+ captor.value.onKeyguardGoingAwayChanged()
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isDreaming() = runBlockingTest {
+ whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(captor.capture())
+
+ captor.value.onDreamingStateChanged(true)
+ assertThat(latest).isTrue()
+
+ captor.value.onDreamingStateChanged(false)
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
fun biometricUnlockState() = runBlockingTest {
val values = mutableListOf<BiometricUnlockModel>()
val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 27d5d0a98978..2b03722f9f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -25,8 +25,8 @@ import android.view.Choreographer.FrameCallback
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -38,7 +38,6 @@ import java.util.UUID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn
@@ -91,18 +90,51 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
}
}
- assertSteps(steps, listWithStep(BigDecimal(.1)))
+ assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN)
job.cancel()
provider.stop()
}
@Test
- fun `startTransition called during another transition fails`() {
- underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, null))
- underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, BOUNCER, null))
+ fun `starting second transition will cancel the first transition`() {
+ runBlocking(IMMEDIATE) {
+ val (animator, provider) = setupAnimator(this)
- assertThat(wtfHandler.failed).isTrue()
+ val steps = mutableListOf<TransitionStep>()
+ val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+
+ underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator))
+ // 3 yields(), alternating with the animator, results in a value 0.1, which can be
+ // canceled and tested against
+ yield()
+ yield()
+ yield()
+
+ // Now start 2nd transition, which will interrupt the first
+ val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+ val (animator2, provider2) = setupAnimator(this)
+ underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, animator2))
+
+ val startTime = System.currentTimeMillis()
+ while (animator2.isRunning()) {
+ yield()
+ if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
+ fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
+ }
+ }
+
+ val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+ assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+ val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.9))
+ assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+ job.cancel()
+ job2.cancel()
+ provider.stop()
+ provider2.stop()
+ }
}
@Test
@@ -165,11 +197,15 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
assertThat(wtfHandler.failed).isTrue()
}
- private fun listWithStep(step: BigDecimal): List<BigDecimal> {
+ private fun listWithStep(
+ step: BigDecimal,
+ start: BigDecimal = BigDecimal.ZERO,
+ stop: BigDecimal = BigDecimal.ONE,
+ ): List<BigDecimal> {
val steps = mutableListOf<BigDecimal>()
- var i = BigDecimal.ZERO
- while (i.compareTo(BigDecimal.ONE) <= 0) {
+ var i = start
+ while (i.compareTo(stop) <= 0) {
steps.add(i)
i = (i + step).setScale(2, RoundingMode.HALF_UP)
}
@@ -177,23 +213,43 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
return steps
}
- private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
+ private fun assertSteps(
+ steps: List<TransitionStep>,
+ fractions: List<BigDecimal>,
+ from: KeyguardState,
+ to: KeyguardState,
+ ) {
assertThat(steps[0])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
+ .isEqualTo(
+ TransitionStep(
+ from,
+ to,
+ fractions[0].toFloat(),
+ TransitionState.STARTED,
+ OWNER_NAME
+ )
+ )
fractions.forEachIndexed { index, fraction ->
assertThat(steps[index + 1])
.isEqualTo(
TransitionStep(
- AOD,
- LOCKSCREEN,
+ from,
+ to,
fraction.toFloat(),
TransitionState.RUNNING,
OWNER_NAME
)
)
}
+ val lastValue = fractions[fractions.size - 1].toFloat()
+ val status =
+ if (lastValue < 1f) {
+ TransitionState.CANCELED
+ } else {
+ TransitionState.FINISHED
+ }
assertThat(steps[steps.size - 1])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME))
+ .isEqualTo(TransitionStep(from, to, lastValue, status, OWNER_NAME))
assertThat(wtfHandler.failed).isFalse()
}
@@ -230,7 +286,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
scope.launch {
frames.collect {
// Delay is required for AnimationHandler to properly register a callback
- delay(1)
+ yield()
val (frameNumber, callback) = it
callback?.doFrame(frameNumber)
}
@@ -243,7 +299,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
}
override fun postFrameCallback(cb: FrameCallback) {
- frames.value = Pair(++frameCount, cb)
+ frames.value = Pair(frameCount++, cb)
}
override fun postCommitCallback(runnable: Runnable) {}
override fun getFrameTime() = frameCount
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index c85f7b9e6885..21e506830d88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -155,6 +155,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
verify(repository).setPrimaryVisible(false)
verify(repository).setPrimaryShow(null)
+ verify(repository).setPrimaryHide(true)
verify(falsingCollector).onBouncerHidden()
verify(mPrimaryBouncerCallbackInteractor).dispatchReset()
verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index cd7a949443c9..72e022ed7bbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -439,6 +439,17 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
verify(mQSPanelController).setExpanded(false);
}
+ @Test
+ public void startsListeningAfterStateChangeToExpanded_inSplitShade() {
+ QSFragment fragment = resumeAndGetFragment();
+ enableSplitShade();
+ fragment.setQsVisible(true);
+ clearInvocations(mQSPanelController);
+
+ fragment.setExpanded(true);
+ verify(mQSPanelController).setListening(true, true);
+ }
+
@Override
protected Fragment instantiate(Context context, String className, Bundle arguments) {
MockitoAnnotations.initMocks(this);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
new file mode 100644
index 000000000000..0aa36218b3a7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 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.screenrecord
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.widget.Spinner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class ScreenRecordPermissionDialogTest : SysuiTestCase() {
+
+ @Mock private lateinit var starter: ActivityStarter
+ @Mock private lateinit var controller: RecordingController
+ @Mock private lateinit var userContextProvider: UserContextProvider
+ @Mock private lateinit var flags: FeatureFlags
+ @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Mock private lateinit var onStartRecordingClicked: Runnable
+
+ private lateinit var dialog: ScreenRecordPermissionDialog
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ dialog =
+ ScreenRecordPermissionDialog(
+ context,
+ controller,
+ starter,
+ dialogLaunchAnimator,
+ userContextProvider,
+ onStartRecordingClicked
+ )
+ dialog.onCreate(null)
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+ }
+
+ @After
+ fun teardown() {
+ if (::dialog.isInitialized) {
+ dialog.dismiss()
+ }
+ }
+
+ @Test
+ fun testShowDialog_partialScreenSharingEnabled_optionsSpinnerIsVisible() {
+ dialog.show()
+
+ val visibility = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner).visibility
+ assertThat(visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun testShowDialog_singleAppSelected_showTapsIsGone() {
+ dialog.show()
+ onSpinnerItemSelected(SINGLE_APP)
+
+ val visibility = dialog.requireViewById<View>(R.id.show_taps).visibility
+ assertThat(visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun testShowDialog_entireScreenSelected_showTapsIsVisible() {
+ dialog.show()
+ onSpinnerItemSelected(ENTIRE_SCREEN)
+
+ val visibility = dialog.requireViewById<View>(R.id.show_taps).visibility
+ assertThat(visibility).isEqualTo(View.VISIBLE)
+ }
+
+ private fun onSpinnerItemSelected(position: Int) {
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ spinner.onItemSelectedListener.onItemSelected(spinner, mock(), position, /* id= */ 0)
+ }
+}
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 1f71e3c64ec4..7d2251e20021 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -94,7 +94,6 @@ import com.android.systemui.DejankUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.doze.DozeLog;
@@ -492,7 +491,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mUnlockedScreenOffAnimationController,
mShadeTransitionController,
systemClock,
- mock(CameraGestureHelper.class),
mKeyguardBottomAreaViewModel,
mKeyguardBottomAreaInteractor,
mDumpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index c6585931a035..77c690a924c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -1421,6 +1421,21 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
}
@Test
+ public void onBiometricError_faceLockedOutSecondTimeOnBouncer_showsUnavailableMessage() {
+ createController();
+ onFaceLockoutError("first lockout");
+ clearInvocations(mRotateTextViewController);
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+
+ onFaceLockoutError("second lockout");
+
+ verify(mStatusBarKeyguardViewManager)
+ .setKeyguardMessage(
+ eq(mContext.getString(R.string.keyguard_face_unlock_unavailable)),
+ any());
+ }
+
+ @Test
public void onBiometricError_faceLockedOutSecondTimeButUdfpsActive_showsNoMessage() {
createController();
onFaceLockoutError("first lockout");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt
new file mode 100644
index 000000000000..62b4e7b79f5e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.connectivity
+
+import android.content.res.Resources
+import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
+
+typealias CarrierId = Int
+
+typealias NetworkType = String
+
+typealias ResId = Int
+
+class MobileIconCarrierIdOverridesFake : MobileIconCarrierIdOverrides {
+ /** Backing for [carrierIdEntryExists] */
+ var overriddenIds = mutableSetOf<Int>()
+
+ /** Backing for [getOverrideFor]. Map should be Map< CarrierId < NetworkType, ResId>> */
+ var overridesByCarrierId = mutableMapOf<CarrierId, Map<NetworkType, ResId>>()
+
+ override fun getOverrideFor(
+ carrierId: CarrierId,
+ networkType: NetworkType,
+ resources: Resources
+ ): ResId {
+ if (!overriddenIds.contains(carrierId)) return 0
+
+ return overridesByCarrierId[carrierId]?.get(networkType) ?: 0
+ }
+
+ override fun carrierIdEntryExists(carrierId: Int): Boolean {
+ return overriddenIds.contains(carrierId)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java
deleted file mode 100644
index 7ddfde370afa..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.connectivity;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-
-import com.android.settingslib.mobile.TelephonyIcons;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class MobileStateTest extends SysuiTestCase {
-
- private final MobileState mState = new MobileState();
-
- @Before
- public void setUp() {
- }
-
- @Test
- public void testIsDataDisabledOrNotDefault_dataDisabled() {
- mState.iconGroup = TelephonyIcons.DATA_DISABLED;
- mState.userSetup = true;
-
- assertTrue(mState.isDataDisabledOrNotDefault());
- }
-
- @Test
- public void testIsDataDisabledOrNotDefault_notDefaultData() {
- mState.iconGroup = TelephonyIcons.NOT_DEFAULT_DATA;
- mState.userSetup = true;
-
- assertTrue(mState.isDataDisabledOrNotDefault());
- }
-
- @Test
- public void testIsDataDisabledOrNotDefault_notDisabled() {
- mState.iconGroup = TelephonyIcons.G;
- mState.userSetup = true;
-
- assertFalse(mState.isDataDisabledOrNotDefault());
- }
-
- @Test
- public void testHasActivityIn_noData_noActivity() {
- mState.dataConnected = false;
- mState.carrierNetworkChangeMode = false;
- mState.activityIn = false;
-
- assertFalse(mState.hasActivityIn());
- }
-
- @Test
- public void testHasActivityIn_noData_activityIn() {
- mState.dataConnected = false;
- mState.carrierNetworkChangeMode = false;
- mState.activityIn = true;
-
- assertFalse(mState.hasActivityIn());
- }
-
- @Test
- public void testHasActivityIn_dataConnected_activityIn() {
- mState.dataConnected = true;
- mState.carrierNetworkChangeMode = false;
- mState.activityIn = true;
-
- assertTrue(mState.hasActivityIn());
- }
-
- @Test
- public void testHasActivityIn_carrierNetworkChange() {
- mState.dataConnected = true;
- mState.carrierNetworkChangeMode = true;
- mState.activityIn = true;
-
- assertFalse(mState.hasActivityIn());
- }
-
- @Test
- public void testHasActivityOut_noData_noActivity() {
- mState.dataConnected = false;
- mState.carrierNetworkChangeMode = false;
- mState.activityOut = false;
-
- assertFalse(mState.hasActivityOut());
- }
-
- @Test
- public void testHasActivityOut_noData_activityOut() {
- mState.dataConnected = false;
- mState.carrierNetworkChangeMode = false;
- mState.activityOut = true;
-
- assertFalse(mState.hasActivityOut());
- }
-
- @Test
- public void testHasActivityOut_dataConnected_activityOut() {
- mState.dataConnected = true;
- mState.carrierNetworkChangeMode = false;
- mState.activityOut = true;
-
- assertTrue(mState.hasActivityOut());
- }
-
- @Test
- public void testHasActivityOut_carrierNetworkChange() {
- mState.dataConnected = true;
- mState.carrierNetworkChangeMode = true;
- mState.activityOut = true;
-
- assertFalse(mState.hasActivityOut());
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
new file mode 100644
index 000000000000..a226ded06111
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.connectivity
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MobileStateTest : SysuiTestCase() {
+
+ private val state = MobileState()
+ @Before fun setUp() {}
+
+ @Test
+ fun testIsDataDisabledOrNotDefault_dataDisabled() {
+ state.iconGroup = TelephonyIcons.DATA_DISABLED
+ state.userSetup = true
+ assertTrue(state.isDataDisabledOrNotDefault)
+ }
+
+ @Test
+ fun testIsDataDisabledOrNotDefault_notDefaultData() {
+ state.iconGroup = TelephonyIcons.NOT_DEFAULT_DATA
+ state.userSetup = true
+ assertTrue(state.isDataDisabledOrNotDefault)
+ }
+
+ @Test
+ fun testIsDataDisabledOrNotDefault_notDisabled() {
+ state.iconGroup = TelephonyIcons.G
+ state.userSetup = true
+ assertFalse(state.isDataDisabledOrNotDefault)
+ }
+
+ @Test
+ fun testHasActivityIn_noData_noActivity() {
+ state.dataConnected = false
+ state.carrierNetworkChangeMode = false
+ state.activityIn = false
+ assertFalse(state.hasActivityIn())
+ }
+
+ @Test
+ fun testHasActivityIn_noData_activityIn() {
+ state.dataConnected = false
+ state.carrierNetworkChangeMode = false
+ state.activityIn = true
+ assertFalse(state.hasActivityIn())
+ }
+
+ @Test
+ fun testHasActivityIn_dataConnected_activityIn() {
+ state.dataConnected = true
+ state.carrierNetworkChangeMode = false
+ state.activityIn = true
+ assertTrue(state.hasActivityIn())
+ }
+
+ @Test
+ fun testHasActivityIn_carrierNetworkChange() {
+ state.dataConnected = true
+ state.carrierNetworkChangeMode = true
+ state.activityIn = true
+ assertFalse(state.hasActivityIn())
+ }
+
+ @Test
+ fun testHasActivityOut_noData_noActivity() {
+ state.dataConnected = false
+ state.carrierNetworkChangeMode = false
+ state.activityOut = false
+ assertFalse(state.hasActivityOut())
+ }
+
+ @Test
+ fun testHasActivityOut_noData_activityOut() {
+ state.dataConnected = false
+ state.carrierNetworkChangeMode = false
+ state.activityOut = true
+ assertFalse(state.hasActivityOut())
+ }
+
+ @Test
+ fun testHasActivityOut_dataConnected_activityOut() {
+ state.dataConnected = true
+ state.carrierNetworkChangeMode = false
+ state.activityOut = true
+ assertTrue(state.hasActivityOut())
+ }
+
+ @Test
+ fun testHasActivityOut_carrierNetworkChange() {
+ state.dataConnected = true
+ state.carrierNetworkChangeMode = true
+ state.activityOut = true
+ assertFalse(state.hasActivityOut())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 9c65fac1af45..9c870b5aa363 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -71,6 +71,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.telephony.TelephonyListenerManager;
@@ -125,6 +126,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
protected CarrierConfigTracker mCarrierConfigTracker;
protected FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
protected Handler mMainHandler;
+ // Use a real mobile mappings object since lots of tests rely on it
+ protected FakeMobileMappingsProxy mMobileMappingsProxy = new FakeMobileMappingsProxy();
protected WifiStatusTrackerFactory mWifiStatusTrackerFactory;
protected MobileSignalControllerFactory mMobileFactory;
@@ -219,10 +222,13 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
mWifiStatusTrackerFactory = new WifiStatusTrackerFactory(
mContext, mMockWm, mMockNsm, mMockCm, mMainHandler);
+ // Most of these tests rely on the actual MobileMappings behavior
+ mMobileMappingsProxy.setUseRealImpl(true);
mMobileFactory = new MobileSignalControllerFactory(
mContext,
mCallbackHandler,
- mCarrierConfigTracker
+ mCarrierConfigTracker,
+ mMobileMappingsProxy
);
mNetworkController = new NetworkControllerImpl(mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index 4bed4a19b3d9..1d112262765e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -18,12 +18,21 @@ package com.android.systemui.statusbar.connectivity;
import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+import static android.telephony.TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED;
+import static android.telephony.TelephonyManager.EXTRA_CARRIER_ID;
+import static android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID;
+import static com.android.settingslib.mobile.TelephonyIcons.NR_5G_PLUS;
+
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.content.Intent;
import android.net.NetworkCapabilities;
import android.os.Handler;
import android.os.Looper;
@@ -35,6 +44,7 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import com.android.settingslib.SignalIcon.MobileIconGroup;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.dump.DumpManager;
@@ -45,6 +55,8 @@ import com.android.systemui.util.CarrierConfigTracker;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.HashMap;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -329,6 +341,57 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest {
assertFalse(mNetworkController.isMobileDataNetworkInService());
}
+ @Test
+ public void mobileSignalController_getsCarrierId() {
+ when(mMockTm.getSimCarrierId()).thenReturn(1);
+ setupDefaultSignal();
+
+ assertEquals(1, mMobileSignalController.getState().getCarrierId());
+ }
+
+ @Test
+ public void mobileSignalController_updatesCarrierId_onChange() {
+ when(mMockTm.getSimCarrierId()).thenReturn(1);
+ setupDefaultSignal();
+
+ // Updates are sent down through this broadcast, we can send the intent directly
+ Intent intent = new Intent(ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED);
+ intent.putExtra(EXTRA_SUBSCRIPTION_ID, mSubId);
+ intent.putExtra(EXTRA_CARRIER_ID, 2);
+
+ mMobileSignalController.handleBroadcast(intent);
+
+ assertEquals(2, mMobileSignalController.getState().getCarrierId());
+ }
+
+ @Test
+ public void networkTypeIcon_hasCarrierIdOverride() {
+ int fakeCarrier = 1;
+ int fakeIconOverride = 12345;
+ int testDataNetType = 100;
+ String testDataString = "100";
+ HashMap<String, MobileIconGroup> testMap = new HashMap<>();
+ testMap.put(testDataString, NR_5G_PLUS);
+
+ // Pretend that there is an override for this icon, and this carrier ID
+ NetworkTypeResIdCache mockCache = mock(NetworkTypeResIdCache.class);
+ when(mockCache.get(eq(NR_5G_PLUS), eq(fakeCarrier), any())).thenReturn(fakeIconOverride);
+
+ // Turn off the default mobile mapping, so we can override
+ mMobileMappingsProxy.setUseRealImpl(false);
+ mMobileMappingsProxy.setIconMap(testMap);
+ // Use the mocked cache
+ mMobileSignalController.mCurrentState.setNetworkTypeResIdCache(mockCache);
+ // Rebuild the network map
+ mMobileSignalController.setConfiguration(mConfig);
+ when(mMockTm.getSimCarrierId()).thenReturn(fakeCarrier);
+
+ setupDefaultSignal();
+ updateDataConnectionState(TelephonyManager.DATA_CONNECTED, testDataNetType);
+
+ verifyDataIndicators(fakeIconOverride);
+ }
+
private void testDataActivity(int direction, boolean in, boolean out) {
updateDataActivity(direction);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
new file mode 100644
index 000000000000..9e73487972e8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.connectivity
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NetworkTypeResIdCacheTest : SysuiTestCase() {
+ private lateinit var cache: NetworkTypeResIdCache
+ private var overrides = MobileIconCarrierIdOverridesFake()
+
+ @Before
+ fun setUp() {
+ cache = NetworkTypeResIdCache(overrides)
+ }
+
+ @Test
+ fun carrier1_noOverride_usesDefault() {
+ assertThat(cache.get(group1, CARRIER_1, context)).isEqualTo(iconDefault1)
+ }
+
+ @Test
+ fun carrier1_overridden_usesOverride() {
+ overrides.overriddenIds.add(CARRIER_1)
+ overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1)
+
+ assertThat(cache.get(group1, CARRIER_1, context)).isEqualTo(iconOverride1)
+ }
+
+ @Test
+ fun carrier1_override_carrier2UsesDefault() {
+ overrides.overriddenIds.add(CARRIER_1)
+ overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1)
+
+ assertThat(cache.get(group1, CARRIER_2, context)).isEqualTo(iconDefault1)
+ }
+
+ @Test
+ fun carrier1_overrideType1_type2UsesDefault() {
+ overrides.overriddenIds.add(CARRIER_1)
+ overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1)
+
+ assertThat(cache.get(group2, CARRIER_1, context)).isEqualTo(iconDefault2)
+ }
+
+ companion object {
+ // Simplified icon overrides here
+ const val CARRIER_1 = 1
+ const val CARRIER_2 = 2
+
+ const val NET_TYPE_1 = "one"
+ const val iconDefault1 = 123
+ const val iconOverride1 = 321
+ val group1 = MobileIconGroup(NET_TYPE_1, /* dataContentDesc */ 0, iconDefault1)
+
+ const val NET_TYPE_2 = "two"
+ const val iconDefault2 = 234
+
+ val group2 = MobileIconGroup(NET_TYPE_2, /* dataContentDesc*/ 0, iconDefault2)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 5ebaf692ab44..d5bfe1f7c2ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -41,6 +41,7 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
@@ -60,6 +61,8 @@ import org.mockito.stubbing.Answer;
import java.util.Optional;
+import dagger.Lazy;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
@@ -84,6 +87,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
@Mock private Vibrator mVibrator;
@Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@Mock private SystemBarAttributesListener mSystemBarAttributesListener;
+ @Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
@@ -115,7 +119,8 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
Optional.of(mVibrator),
new DisableFlagsLogger(),
DEFAULT_DISPLAY,
- mSystemBarAttributesListener);
+ mSystemBarAttributesListener,
+ mCameraLauncherLazy);
when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
when(mRemoteInputQuickSettingsDisabler.adjustDisableFlags(anyInt()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 5ad1431cc8d8..41912f51db56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -112,6 +112,7 @@ import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelView;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
@@ -288,6 +289,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private DeviceStateManager mDeviceStateManager;
@Mock private WiredChargingRippleController mWiredChargingRippleController;
+ @Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
+ @Mock private CameraLauncher mCameraLauncher;
/**
* The process of registering/unregistering a predictive back callback requires a
* ViewRootImpl, which is present IRL, but may be missing during a Mockito unit test.
@@ -380,6 +383,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
+ when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher);
when(mStatusBarComponentFactory.create()).thenReturn(mCentralSurfacesComponent);
when(mCentralSurfacesComponent.getNotificationShadeWindowViewController()).thenReturn(
@@ -481,7 +485,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mActivityLaunchAnimator,
mJankMonitor,
mDeviceStateManager,
- mWiredChargingRippleController, mDreamManager) {
+ mWiredChargingRippleController,
+ mDreamManager,
+ mCameraLauncherLazy) {
@Override
protected ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -893,7 +899,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mCentralSurfaces.showKeyguardImpl();
// Starting a pulse should change the scrim controller to the pulsing state
- when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(true);
+ when(mCameraLauncher.isLaunchingAffordance()).thenReturn(true);
mCentralSurfaces.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
}
@@ -929,7 +935,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mCentralSurfaces.showKeyguardImpl();
// Starting a pulse should change the scrim controller to the pulsing state
- when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(false);
+ when(mCameraLauncher.isLaunchingAffordance()).thenReturn(false);
mCentralSurfaces.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index df48e1d43584..808abc8e9de5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -1437,6 +1437,17 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ public void behindTint_inKeyguardState_bouncerNotActive_usesKeyguardBehindTint() {
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
+ mScrimController.setClipsQsScrim(false);
+
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ finishAnimationsImmediately();
+ assertThat(mScrimBehind.getTint())
+ .isEqualTo(ScrimState.KEYGUARD.getBehindTint());
+ }
+
+ @Test
public void testNotificationTransparency_followsTransitionToFullShade() {
mScrimController.transitionTo(SHADE_LOCKED);
mScrimController.setRawPanelExpansionFraction(1.0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
index 6d8d902615de..a052008d4832 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
@@ -16,31 +16,59 @@
package com.android.systemui.statusbar.pipeline.mobile.util
+import android.telephony.TelephonyDisplayInfo
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.settingslib.mobile.TelephonyIcons
class FakeMobileMappingsProxy : MobileMappingsProxy {
+ // The old [NetworkControllerDataTest] infra requires us to be able to use the real
+ // impl sometimes
+ var useRealImpl = false
+
+ private var realImpl = MobileMappingsProxyImpl()
private var iconMap = mapOf<String, MobileIconGroup>()
private var defaultIcons = TelephonyIcons.THREE_G
fun setIconMap(map: Map<String, MobileIconGroup>) {
iconMap = map
}
- override fun mapIconSets(config: Config): Map<String, MobileIconGroup> = iconMap
+ override fun mapIconSets(config: Config): Map<String, MobileIconGroup> {
+ if (useRealImpl) {
+ return realImpl.mapIconSets(config)
+ }
+ return iconMap
+ }
fun getIconMap() = iconMap
fun setDefaultIcons(group: MobileIconGroup) {
defaultIcons = group
}
- override fun getDefaultIcons(config: Config): MobileIconGroup = defaultIcons
+ override fun getDefaultIcons(config: Config): MobileIconGroup {
+ if (useRealImpl) {
+ return realImpl.getDefaultIcons(config)
+ }
+ return defaultIcons
+ }
+
+ /** This is only used in the old pipeline, use the real impl always */
+ override fun getIconKey(displayInfo: TelephonyDisplayInfo): String {
+ return realImpl.getIconKey(displayInfo)
+ }
+
fun getDefaultIcons(): MobileIconGroup = defaultIcons
override fun toIconKey(networkType: Int): String {
+ if (useRealImpl) {
+ return realImpl.toIconKeyOverride(networkType)
+ }
return networkType.toString()
}
override fun toIconKeyOverride(networkType: Int): String {
+ if (useRealImpl) {
+ return realImpl.toIconKeyOverride(networkType)
+ }
return toIconKey(networkType) + "_override"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 9dea48e3b47c..8572478589fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -123,23 +123,23 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
}
@Test
- fun displayView_screenOff_wakeLockAcquired() {
+ fun displayView_wakeLockAcquired() {
underTest.displayView(getState())
assertThat(fakeWakeLock.isHeld).isTrue()
}
@Test
- fun displayView_screenAlreadyOn_wakeLockNotAcquired() {
+ fun displayView_screenAlreadyOn_wakeLockAcquired() {
whenever(powerManager.isScreenOn).thenReturn(true)
underTest.displayView(getState())
- assertThat(fakeWakeLock.isHeld).isFalse()
+ assertThat(fakeWakeLock.isHeld).isTrue()
}
@Test
- fun displayView_screenOff_wakeLockCanBeReleasedAfterTimeOut() {
+ fun displayView_wakeLockCanBeReleasedAfterTimeOut() {
underTest.displayView(getState())
assertThat(fakeWakeLock.isHeld).isTrue()
@@ -149,6 +149,16 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
}
@Test
+ fun displayView_removeView_wakeLockCanBeReleased() {
+ underTest.displayView(getState())
+ assertThat(fakeWakeLock.isHeld).isTrue()
+
+ underTest.removeView("test reason")
+
+ assertThat(fakeWakeLock.isHeld).isFalse()
+ }
+
+ @Test
fun displayView_twice_viewNotAddedTwice() {
underTest.displayView(getState())
reset(windowManager)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 6f70f0ee0f2b..a798f403c73a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -44,6 +44,9 @@ class FakeKeyguardRepository : KeyguardRepository {
private val _isDozing = MutableStateFlow(false)
override val isDozing: Flow<Boolean> = _isDozing
+ private val _isDreaming = MutableStateFlow(false)
+ override val isDreaming: Flow<Boolean> = _isDreaming
+
private val _dozeAmount = MutableStateFlow(0f)
override val dozeAmount: Flow<Float> = _dozeAmount
@@ -54,10 +57,13 @@ class FakeKeyguardRepository : KeyguardRepository {
override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
private val _isUdfpsSupported = MutableStateFlow(false)
-
+
private val _isBouncerShowing = MutableStateFlow(false)
override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing
+ private val _isKeyguardGoingAway = MutableStateFlow(false)
+ override val isKeyguardGoingAway: Flow<Boolean> = _isKeyguardGoingAway
+
private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index e40f001f27d5..933d2596aed8 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -473,18 +473,6 @@ public abstract class SystemService {
}
/**
- * The {@link UserManager#isUserVisible() user visibility} changed.
- *
- * <p>This callback is called before the user starts or is switched to (or after it stops), when
- * its visibility changed because of that action.
- *
- * @hide
- */
- // NOTE: change visible to int if this method becomes a @SystemApi
- public void onUserVisibilityChanged(@NonNull TargetUser user, boolean visible) {
- }
-
- /**
* Called when an existing user is stopping, for system services to finalize any per-user
* state they maintain for running users. This is called prior to sending the SHUTDOWN
* broadcast to the user; it is a good place to stop making use of any resources of that
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 953e85014535..c3cd1359cd5f 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -82,10 +82,6 @@ public final class SystemServiceManager implements Dumpable {
private static final String USER_STOPPING = "Stop"; // Logged as onUserStopping()
private static final String USER_STOPPED = "Cleanup"; // Logged as onUserStopped()
private static final String USER_COMPLETED_EVENT = "CompletedEvent"; // onUserCompletedEvent()
- private static final String USER_VISIBLE = "Visible"; // Logged on onUserVisible() and
- // onUserStarting() (when visible is true)
- private static final String USER_INVISIBLE = "Invisible"; // Logged on onUserStopping()
- // (when visibilityChanged is true)
// The default number of threads to use if lifecycle thread pool is enabled.
private static final int DEFAULT_MAX_USER_POOL_THREADS = 3;
@@ -354,58 +350,17 @@ public final class SystemServiceManager implements Dumpable {
/**
* Starts the given user.
*/
- public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId,
- boolean visible) {
- EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId, visible ? 1 : 0);
+ public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
+ EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
final TargetUser targetUser = newTargetUser(userId);
synchronized (mTargetUsers) {
mTargetUsers.put(userId, targetUser);
}
-
- if (visible) {
- // Must send the user visiiblity change first, for 2 reasons:
- // 1. Automotive need to update the user-zone mapping ASAP and it's one of the few
- // services listening to this event (OTOH, there are manyy listeners to USER_STARTING
- // and some can take a while to process it)
- // 2. When a user is switched from bg to fg, the onUserVisibilityChanged() callback is
- // called onUserSwitching(), so calling it before onUserStarting() make it more
- // consistent with that
- EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1);
- onUser(t, USER_VISIBLE, /* prevUser= */ null, targetUser);
- }
onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
}
/**
- * Updates the user visibility.
- *
- * <p><b>NOTE: </b>this method should only be called when a user that is already running become
- * visible; if the user is starting visible, callers should call
- * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead.
- */
- public void onUserVisible(@UserIdInt int userId) {
- EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1);
- onUser(USER_VISIBLE, userId);
- }
-
- /**
- * Updates the visibility of the system user.
- *
- * <p>Since the system user never stops, this method must be called when it's switched from / to
- * foreground.
- */
- public void onSystemUserVisibilityChanged(boolean visible) {
- int userId = UserHandle.USER_SYSTEM;
- EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
- if (visible) {
- onUser(USER_VISIBLE, userId);
- } else {
- onUser(USER_INVISIBLE, userId);
- }
- }
-
- /**
* Unlocks the given user.
*/
public void onUserUnlocking(@UserIdInt int userId) {
@@ -452,12 +407,9 @@ public final class SystemServiceManager implements Dumpable {
/**
* Stops the given user.
*/
- public void onUserStopping(@UserIdInt int userId, boolean visibilityChanged) {
- EventLog.writeEvent(EventLogTags.SSM_USER_STOPPING, userId, visibilityChanged ? 1 : 0);
+ public void onUserStopping(@UserIdInt int userId) {
+ EventLog.writeEvent(EventLogTags.SSM_USER_STOPPING, userId);
onUser(USER_STOPPING, userId);
- if (visibilityChanged) {
- onUser(USER_INVISIBLE, userId);
- }
}
/**
@@ -580,12 +532,6 @@ public final class SystemServiceManager implements Dumpable {
threadPool.submit(getOnUserCompletedEventRunnable(
t, service, serviceName, curUser, completedEventType));
break;
- case USER_VISIBLE:
- service.onUserVisibilityChanged(curUser, /* visible= */ true);
- break;
- case USER_INVISIBLE:
- service.onUserVisibilityChanged(curUser, /* visible= */ false);
- break;
default:
throw new IllegalArgumentException(onWhat + " what?");
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2761a864f22f..af6eaf4e94bd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19050,6 +19050,10 @@ public class ActivityManagerService extends IActivityManager.Stub
return mOomAdjuster.mCachedAppOptimizer.useFreezer();
}
+ public boolean isAppFreezerExemptInstPkg() {
+ return mOomAdjuster.mCachedAppOptimizer.freezerExemptInstPkg();
+ }
+
/**
* Resets the state of the {@link com.android.server.am.AppErrors} instance.
* This is intended for testing within the CTS only and is protected by
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index c3839a99ba52..3dee2627e45d 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -37,6 +37,7 @@ import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER;
import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
+import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
@@ -239,7 +240,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
case MSG_DELIVERY_TIMEOUT_SOFT: {
synchronized (mService) {
- deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj);
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final BroadcastProcessQueue queue = (BroadcastProcessQueue) args.arg1;
+ final long originalTimeout = args.argl1;
+ args.recycle();
+ deliveryTimeoutSoftLocked(queue, originalTimeout);
}
return true;
}
@@ -599,6 +604,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// If nothing to dispatch, send any pending result immediately
if (r.receivers.isEmpty()) {
scheduleResultTo(r);
+ notifyFinishBroadcast(r);
}
traceEnd(cookie);
@@ -746,8 +752,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = queue;
+ args.argl1 = timeout;
mLocalHandler.sendMessageDelayed(
- Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_SOFT, queue), timeout);
+ Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_SOFT, args), timeout);
}
if (r.allowBackgroundActivityStarts) {
@@ -834,15 +843,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
r.resultTo = null;
}
- private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue) {
+ private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue,
+ @DurationMillisLong long originalTimeout) {
if (queue.app != null) {
// Instead of immediately triggering an ANR, extend the timeout by
// the amount of time the process was runnable-but-waiting; we're
// only willing to do this once before triggering an hard ANR
final long cpuDelayTime = queue.app.getCpuDelayTime() - queue.lastCpuDelayTime;
- final long timeout = MathUtils.constrain(cpuDelayTime, 0, mConstants.TIMEOUT);
+ final long hardTimeout = MathUtils.constrain(cpuDelayTime, 0, originalTimeout);
mLocalHandler.sendMessageDelayed(
- Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue), timeout);
+ Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue), hardTimeout);
} else {
deliveryTimeoutHardLocked(queue);
}
@@ -1402,30 +1412,34 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
final boolean recordFinished = (r.terminalCount == r.receivers.size());
if (recordFinished) {
- mService.notifyBroadcastFinishedLocked(r);
- mHistory.addBroadcastToHistoryLocked(r);
-
- r.finishTime = SystemClock.uptimeMillis();
- r.nextReceiver = r.receivers.size();
- BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+ notifyFinishBroadcast(r);
+ }
+ }
- if (r.intent.getComponent() == null && r.intent.getPackage() == null
- && (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
- int manifestCount = 0;
- int manifestSkipCount = 0;
- for (int i = 0; i < r.receivers.size(); i++) {
- if (r.receivers.get(i) instanceof ResolveInfo) {
- manifestCount++;
- if (r.delivery[i] == BroadcastRecord.DELIVERY_SKIPPED) {
- manifestSkipCount++;
- }
+ private void notifyFinishBroadcast(@NonNull BroadcastRecord r) {
+ mService.notifyBroadcastFinishedLocked(r);
+ mHistory.addBroadcastToHistoryLocked(r);
+
+ r.finishTime = SystemClock.uptimeMillis();
+ r.nextReceiver = r.receivers.size();
+ BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+
+ if (r.intent.getComponent() == null && r.intent.getPackage() == null
+ && (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+ int manifestCount = 0;
+ int manifestSkipCount = 0;
+ for (int i = 0; i < r.receivers.size(); i++) {
+ if (r.receivers.get(i) instanceof ResolveInfo) {
+ manifestCount++;
+ if (r.delivery[i] == BroadcastRecord.DELIVERY_SKIPPED) {
+ manifestSkipCount++;
}
}
-
- final long dispatchTime = SystemClock.uptimeMillis() - r.enqueueTime;
- mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
- manifestCount, manifestSkipCount, dispatchTime);
}
+
+ final long dispatchTime = SystemClock.uptimeMillis() - r.enqueueTime;
+ mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
+ manifestCount, manifestSkipCount, dispatchTime);
}
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index cbf0aae50e87..2d7b0dc7b536 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -89,6 +89,8 @@ public final class CachedAppOptimizer {
"compact_proc_state_throttle";
@VisibleForTesting static final String KEY_FREEZER_DEBOUNCE_TIMEOUT =
"freeze_debounce_timeout";
+ @VisibleForTesting static final String KEY_FREEZER_EXEMPT_INST_PKG =
+ "freeze_exempt_inst_pkg";
// RSS Indices
private static final int RSS_TOTAL_INDEX = 0;
@@ -137,6 +139,7 @@ public final class CachedAppOptimizer {
@VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
@VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 600_000L;
+ @VisibleForTesting static final Boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = true;
@VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor(
Settings.Global.CACHED_APPS_FREEZER_ENABLED);
@@ -277,6 +280,8 @@ public final class CachedAppOptimizer {
for (String name : properties.getKeyset()) {
if (KEY_FREEZER_DEBOUNCE_TIMEOUT.equals(name)) {
updateFreezerDebounceTimeout();
+ } else if (KEY_FREEZER_EXEMPT_INST_PKG.equals(name)) {
+ updateFreezerExemptInstPkg();
}
}
}
@@ -357,6 +362,7 @@ public final class CachedAppOptimizer {
private boolean mFreezerOverride = false;
@VisibleForTesting volatile long mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
+ @VisibleForTesting volatile boolean mFreezerExemptInstPkg = DEFAULT_FREEZER_EXEMPT_INST_PKG;
// Maps process ID to last compaction statistics for processes that we've fully compacted. Used
// when evaluating throttles that we only consider for "full" compaction, so we don't store
@@ -566,6 +572,15 @@ public final class CachedAppOptimizer {
}
}
+ /**
+ * Returns whether freezer exempts INSTALL_PACKAGES.
+ */
+ public boolean freezerExemptInstPkg() {
+ synchronized (mPhenotypeFlagLock) {
+ return mUseFreezer && mFreezerExemptInstPkg;
+ }
+ }
+
@GuardedBy("mProcLock")
void dump(PrintWriter pw) {
pw.println("CachedAppOptimizer settings");
@@ -647,6 +662,7 @@ public final class CachedAppOptimizer {
pw.println(" " + KEY_USE_FREEZER + "=" + mUseFreezer);
pw.println(" " + KEY_FREEZER_STATSD_SAMPLE_RATE + "=" + mFreezerStatsdSampleRate);
pw.println(" " + KEY_FREEZER_DEBOUNCE_TIMEOUT + "=" + mFreezerDebounceTimeout);
+ pw.println(" " + KEY_FREEZER_EXEMPT_INST_PKG + "=" + mFreezerExemptInstPkg);
synchronized (mProcLock) {
int size = mFrozenProcesses.size();
pw.println(" Apps frozen: " + size);
@@ -1007,6 +1023,7 @@ public final class CachedAppOptimizer {
KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) {
mUseFreezer = isFreezerSupported();
updateFreezerDebounceTimeout();
+ updateFreezerExemptInstPkg();
} else {
mUseFreezer = false;
}
@@ -1194,6 +1211,15 @@ public final class CachedAppOptimizer {
if (mFreezerDebounceTimeout < 0) {
mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
}
+ Slog.d(TAG_AM, "Freezer timeout set to " + mFreezerDebounceTimeout);
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateFreezerExemptInstPkg() {
+ mFreezerExemptInstPkg = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+ KEY_FREEZER_EXEMPT_INST_PKG, DEFAULT_FREEZER_EXEMPT_INST_PKG);
+ Slog.d(TAG_AM, "Freezer exemption set to " + mFreezerExemptInstPkg);
}
private boolean parseProcStateThrottle(String procStateThrottleString) {
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 60e6754f4b6f..ea3c8dc1fa90 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -107,21 +107,21 @@ option java_package com.android.server.am
30079 uc_dispatch_user_switch (oldUserId|1|5),(newUserId|1|5)
30080 uc_continue_user_switch (oldUserId|1|5),(newUserId|1|5)
30081 uc_send_user_broadcast (userId|1|5),(IntentAction|3)
+
# Tags below are used by SystemServiceManager - although it's technically part of am, these are
# also user switch events and useful to be analyzed together with events above.
-30082 ssm_user_starting (userId|1|5),(visible|1)
+30082 ssm_user_starting (userId|1|5)
30083 ssm_user_switching (oldUserId|1|5),(newUserId|1|5)
30084 ssm_user_unlocking (userId|1|5)
30085 ssm_user_unlocked (userId|1|5)
-30086 ssm_user_stopping (userId|1|5),(visibilityChanged|1)
+30086 ssm_user_stopping (userId|1|5)
30087 ssm_user_stopped (userId|1|5)
30088 ssm_user_completed_event (userId|1|5),(eventFlag|1|5)
-30089 ssm_user_visibility_changed (userId|1|5),(visible|1)
+
+# Similarly, tags below are used by UserManagerService
+30091 um_user_visibility_changed (userId|1|5),(visible|1)
# Foreground service start/stop events.
30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
30101 am_foreground_service_denied (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
30102 am_foreground_service_stop (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
-
-
-
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 42bfc4cc7130..ecea96e927e3 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1693,7 +1693,8 @@ public final class ProcessList {
app.info.packageName);
externalStorageAccess = storageManagerInternal.hasExternalStorageAccess(uid,
app.info.packageName);
- if (pm.checkPermission(Manifest.permission.INSTALL_PACKAGES,
+ if (mService.isAppFreezerExemptInstPkg()
+ && pm.checkPermission(Manifest.permission.INSTALL_PACKAGES,
app.info.packageName, userId)
== PackageManager.PERMISSION_GRANTED) {
Slog.i(TAG, app.info.packageName + " is exempt from freezer");
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 8049a3adeb5f..92133274c0cc 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -177,9 +177,7 @@ class UserController implements Handler.Callback {
static final int START_USER_SWITCH_FG_MSG = 120;
static final int COMPLETE_USER_SWITCH_MSG = 130;
static final int USER_COMPLETED_EVENT_MSG = 140;
- static final int USER_VISIBLE_MSG = 150;
-
- private static final int NO_ARG2 = 0;
+ static final int USER_VISIBILITY_CHANGED_MSG = 150;
// Message constant to clear {@link UserJourneySession} from {@link mUserIdToUserJourneyMap} if
// the user journey, defined in the UserLifecycleJourneyReported atom for statsd, is not
@@ -439,10 +437,13 @@ class UserController implements Handler.Callback {
/** @see #getLastUserUnlockingUptime */
private volatile long mLastUserUnlockingUptime = 0;
+ // TODO(b/244333150) remove this array and let UserVisibilityMediator call the listeners
+ // directly, as that class should be responsible for all user visibility logic (for example,
+ // when the foreground user is switched out, its profiles also become invisible)
/**
* List of visible users (as defined by {@link UserManager#isUserVisible()}).
*
- * <p>It's only used to call {@link SystemServiceManager} when the visibility is changed upon
+ * <p>It's only used to call {@link UserManagerInternal} when the visibility is changed upon
* the user starting or stopping.
*
* <p>Note: only the key is used, not the value.
@@ -1096,10 +1097,7 @@ class UserController implements Handler.Callback {
synchronized (mLock) {
visibleBefore = mVisibleUsers.get(userId);
if (visibleBefore) {
- if (DEBUG_MU) {
- Slogf.d(TAG, "Removing %d from mVisibleUsers", userId);
- }
- mVisibleUsers.delete(userId);
+ deleteVisibleUserLocked(userId);
visibilityChanged = true;
} else {
visibilityChanged = false;
@@ -1148,6 +1146,20 @@ class UserController implements Handler.Callback {
}
}
+ private void addVisibleUserLocked(@UserIdInt int userId) {
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "adding %d to mVisibleUsers", userId);
+ }
+ mVisibleUsers.put(userId, true);
+ }
+
+ private void deleteVisibleUserLocked(@UserIdInt int userId) {
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "deleting %d from mVisibleUsers", userId);
+ }
+ mVisibleUsers.delete(userId);
+ }
+
private void finishUserStopping(final int userId, final UserState uss,
final boolean allowDelayedLocking, final boolean visibilityChanged) {
EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPING, userId);
@@ -1166,7 +1178,10 @@ class UserController implements Handler.Callback {
mInjector.batteryStatsServiceNoteEvent(
BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
Integer.toString(userId), userId);
- mInjector.getSystemServiceManager().onUserStopping(userId, visibilityChanged);
+ mInjector.getSystemServiceManager().onUserStopping(userId);
+ if (visibilityChanged) {
+ mInjector.onUserVisibilityChanged(userId, /* visible= */ false);
+ }
Runnable finishUserStoppedAsync = () ->
mHandler.post(() -> finishUserStopped(uss, allowDelayedLocking));
@@ -1635,7 +1650,12 @@ class UserController implements Handler.Callback {
return false;
}
- mInjector.getUserManagerInternal().assignUserToDisplay(userId, displayId);
+ if (!userInfo.preCreated) {
+ // TODO(b/244644281): UMI should return whether the user is visible. And if fails,
+ // the user should not be in the mediator's started users structure
+ mInjector.getUserManagerInternal().assignUserToDisplay(userId,
+ userInfo.profileGroupId, foreground, displayId);
+ }
// TODO(b/239982558): might need something similar for bg users on secondary display
if (foreground && isUserSwitchUiEnabled()) {
@@ -1687,12 +1707,23 @@ class UserController implements Handler.Callback {
// Make sure the old user is no longer considering the display to be on.
mInjector.reportGlobalUsageEvent(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
boolean userSwitchUiEnabled;
+ // TODO(b/244333150): temporary state until the callback logic is moved to
+ // UserVisibilityManager
+ int previousCurrentUserId; boolean notifyPreviousCurrentUserId;
synchronized (mLock) {
+ previousCurrentUserId = mCurrentUserId;
+ notifyPreviousCurrentUserId = mVisibleUsers.get(previousCurrentUserId);
+ if (notifyPreviousCurrentUserId) {
+ deleteVisibleUserLocked(previousCurrentUserId);
+ }
mCurrentUserId = userId;
mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
userSwitchUiEnabled = mUserSwitchUiEnabled;
}
mInjector.updateUserConfiguration();
+ // TODO(b/244644281): updateProfileRelatedCaches() is called on both if and else
+ // parts, ideally it should be moved outside, but for now it's not as there are many
+ // calls to external components here afterwards
updateProfileRelatedCaches();
mInjector.getWindowManager().setCurrentUser(userId);
mInjector.reportCurWakefulnessUsageEvent();
@@ -1705,6 +1736,11 @@ class UserController implements Handler.Callback {
mInjector.getWindowManager().lockNow(null);
}
}
+ if (notifyPreviousCurrentUserId) {
+ mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG,
+ previousCurrentUserId, 0));
+ }
+
} else {
final Integer currentUserIdInt = mCurrentUserId;
updateProfileRelatedCaches();
@@ -1730,10 +1766,7 @@ class UserController implements Handler.Callback {
&& mInjector.getUserManagerInternal().isUserVisible(userId);
if (visible) {
synchronized (mLock) {
- if (DEBUG_MU) {
- Slogf.d(TAG, "Adding %d to mVisibleUsers", userId);
- }
- mVisibleUsers.put(userId, true);
+ addVisibleUserLocked(userId);
}
}
@@ -1776,12 +1809,15 @@ class UserController implements Handler.Callback {
mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId,
visible ? 1 : 0));
t.traceEnd();
- } else if (visible) {
+ }
+
+ if (visible) {
// User was already running and became visible (for example, when switching to a
// user that was started in the background before), so it's necessary to explicitly
// notify the services (while when the user starts from BOOTING, USER_START_MSG
// takes care of that.
- mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBLE_MSG, userId, NO_ARG2));
+ mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, userId,
+ visible ? 1 : 0));
}
t.traceBegin("sendMessages");
@@ -2084,6 +2120,8 @@ class UserController implements Handler.Callback {
}
private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("timeoutUserSwitch-" + oldUserId + "-to-" + newUserId);
synchronized (mLock) {
Slogf.e(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
mTimeoutUserSwitchCallbacks = mCurWaitingUserSwitchCallbacks;
@@ -2093,6 +2131,7 @@ class UserController implements Handler.Callback {
mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_CALLBACKS_TIMEOUT_MSG,
oldUserId, newUserId), USER_SWITCH_CALLBACKS_TIMEOUT_MS);
}
+ t.traceEnd();
}
private void timeoutUserSwitchCallbacks(int oldUserId, int newUserId) {
@@ -2150,6 +2189,8 @@ class UserController implements Handler.Callback {
+ " ms after dispatchUserSwitch.");
}
+ TimingsTraceAndSlog t2 = new TimingsTraceAndSlog(TAG);
+ t2.traceBegin("onUserSwitchingReply-" + name);
curWaitingUserSwitchCallbacks.remove(name);
// Continue switching if all callbacks have been notified and
// user switching session is still valid
@@ -2158,11 +2199,15 @@ class UserController implements Handler.Callback {
== mCurWaitingUserSwitchCallbacks)) {
sendContinueUserSwitchLU(uss, oldUserId, newUserId);
}
+ t2.traceEnd();
}
}
};
+ t.traceBegin("onUserSwitching-" + name);
mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(newUserId, callback);
+ t.traceEnd();
} catch (RemoteException e) {
+ // Ignore
}
}
} else {
@@ -2176,10 +2221,13 @@ class UserController implements Handler.Callback {
@GuardedBy("mLock")
private void sendContinueUserSwitchLU(UserState uss, int oldUserId, int newUserId) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("sendContinueUserSwitchLU-" + oldUserId + "-to-" + newUserId);
mCurWaitingUserSwitchCallbacks = null;
mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
oldUserId, newUserId, uss));
+ t.traceEnd();
}
@VisibleForTesting
@@ -2561,7 +2609,8 @@ class UserController implements Handler.Callback {
if (!UserManager.isHeadlessSystemUserMode()) {
// Don't need to call on HSUM because it will be called when the system user is
// restarted on background
- mInjector.onUserStarting(UserHandle.USER_SYSTEM, /* visible= */ true);
+ mInjector.onUserStarting(UserHandle.USER_SYSTEM);
+ mInjector.onUserVisibilityChanged(UserHandle.USER_SYSTEM, /* visible= */ true);
}
}
@@ -2573,12 +2622,12 @@ class UserController implements Handler.Callback {
int userId = UserHandle.USER_SYSTEM;
synchronized (mLock) {
if (visible) {
- mVisibleUsers.put(userId, true);
+ addVisibleUserLocked(userId);
} else {
- mVisibleUsers.delete(userId);
+ deleteVisibleUserLocked(userId);
}
}
- mInjector.notifySystemUserVisibilityChanged(visible);
+ mInjector.onUserVisibilityChanged(userId, visible);
t.traceEnd();
}
@@ -3078,7 +3127,7 @@ class UserController implements Handler.Callback {
logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
USER_LIFECYCLE_EVENT_STATE_BEGIN);
- mInjector.onUserStarting(/* userId= */ msg.arg1, /* visible= */ msg.arg2 == 1);
+ mInjector.onUserStarting(/* userId= */ msg.arg1);
scheduleOnUserCompletedEvent(msg.arg1,
UserCompletedEventType.EVENT_TYPE_USER_STARTING,
USER_COMPLETED_EVENT_DELAY_MS);
@@ -3159,8 +3208,9 @@ class UserController implements Handler.Callback {
case COMPLETE_USER_SWITCH_MSG:
completeUserSwitch(msg.arg1);
break;
- case USER_VISIBLE_MSG:
- mInjector.getSystemServiceManager().onUserVisible(/* userId= */ msg.arg1);
+ case USER_VISIBILITY_CHANGED_MSG:
+ mInjector.onUserVisibilityChanged(/* userId= */ msg.arg1,
+ /* visible= */ msg.arg2 == 1);
break;
}
return false;
@@ -3692,12 +3742,12 @@ class UserController implements Handler.Callback {
return UserManager.isUsersOnSecondaryDisplaysEnabled();
}
- void onUserStarting(int userId, boolean visible) {
- getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId,
- visible);
+ void onUserStarting(@UserIdInt int userId) {
+ getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId);
}
- void notifySystemUserVisibilityChanged(boolean visible) {
- getSystemServiceManager().onSystemUserVisibilityChanged(visible);
+
+ void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
+ getUserManagerInternal().onUserVisibilityChanged(userId, visible);
}
}
}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index efa2f25bf3e3..31d707da014f 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -883,6 +883,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
@Override
public void onUserStarting(@NonNull TargetUser user) {
+ Slog.d(TAG, "Starting user " + user.getUserIdentifier());
mService.onUserStarting(user,
Environment.getDataSystemDeDirectory(user.getUserIdentifier()));
}
@@ -1047,6 +1048,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
"com.android.server.app.GameManagerService");
if (!mSettings.containsKey(userId)) {
+ Slog.d(TAG, "Failed to set game mode for package " + packageName
+ + " as user " + userId + " is not started");
return;
}
GameManagerSettings userSettings = mSettings.get(userId);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index d35d193ebff2..78b697d13f7b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1716,7 +1716,20 @@ public final class DisplayManagerService extends SystemService {
final Point userPreferredResolution =
mPersistentDataStore.getUserPreferredResolution(device);
final float refreshRate = mPersistentDataStore.getUserPreferredRefreshRate(device);
- if (userPreferredResolution == null && Float.isNaN(refreshRate)) {
+ // If value in persistentDataStore is null, preserving the mode from systemPreferredMode.
+ // This is required because in some devices, user-preferred mode was not stored in
+ // persistentDataStore, but was stored in a config which is returned through
+ // systemPreferredMode.
+ if ((userPreferredResolution == null && Float.isNaN(refreshRate))
+ || (userPreferredResolution.equals(0, 0) && refreshRate == 0.0f)) {
+ Display.Mode systemPreferredMode = device.getSystemPreferredDisplayModeLocked();
+ if (systemPreferredMode == null) {
+ return;
+ }
+ storeModeInPersistentDataStoreLocked(
+ display.getDisplayIdLocked(), systemPreferredMode.getPhysicalWidth(),
+ systemPreferredMode.getPhysicalHeight(), systemPreferredMode.getRefreshRate());
+ device.setUserPreferredDisplayModeLocked(systemPreferredMode);
return;
}
Display.Mode.Builder modeBuilder = new Display.Mode.Builder();
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index dcec0aaf9358..2669d217120f 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -140,7 +140,9 @@ import com.android.server.location.provider.StationaryThrottlingLocationProvider
import com.android.server.location.provider.proxy.ProxyLocationProvider;
import com.android.server.location.settings.LocationSettings;
import com.android.server.location.settings.LocationUserSettings;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
+import com.android.server.utils.Slogf;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -308,6 +310,10 @@ public class LocationManagerService extends ILocationManager.Stub implements
permissionManagerInternal.setLocationExtraPackagesProvider(
userId -> mContext.getResources().getStringArray(
com.android.internal.R.array.config_locationExtraPackageNames));
+
+ // TODO(b/241604546): properly handle this callback
+ LocalServices.getService(UserManagerInternal.class).addUserVisibilityListener(
+ (u, v) -> Slogf.i(TAG, "onUserVisibilityChanged(): %d -> %b", u, v));
}
@Nullable
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 56ec8e46291d..91558308e305 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -77,6 +77,23 @@ public abstract class UserManagerInternal {
}
/**
+ * Listener for {@link UserManager#isUserVisible() user visibility} changes.
+ */
+ public interface UserVisibilityListener {
+
+ /**
+ * Called when the {@link UserManager#isUserVisible() user visibility} changed.
+ *
+ * <p><b>Note:</b> this method is called independently of
+ * {@link com.android.server.SystemService} callbacks; for example, the call with
+ * {@code visible} {@code true} might be called before the
+ * {@link com.android.server.SystemService#onUserStarting(com.android.server.SystemService.TargetUser)}
+ * call.
+ */
+ void onUserVisibilityChanged(@UserIdInt int userId, boolean visible);
+ }
+
+ /**
* Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to set
* restrictions enforced by the user.
*
@@ -331,13 +348,18 @@ public abstract class UserManagerInternal {
* <p>On most devices this call will be a no-op, but it will be used on devices that support
* multiple users on multiple displays (like automotives with passenger displays).
*
+ * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
+ * is started)
+ *
* <p><b>NOTE: </b>this method doesn't validate if the display exists, it's up to the caller to
* check it. In fact, one of the intended clients for this method is
* {@code DisplayManagerService}, which will call it when a virtual display is created (another
* client is {@code UserController}, which will call it when a user is started).
- *
*/
- public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId);
+ // TODO(b/244644281): rename to assignUserToDisplayOnStart() and make sure it's called on boot
+ // as well
+ public abstract void assignUserToDisplay(@UserIdInt int userId, @UserIdInt int profileGroupId,
+ boolean foreground, int displayId);
/**
* Unassigns a user from its current display.
@@ -346,7 +368,7 @@ public abstract class UserManagerInternal {
* multiple users on multiple displays (like automotives with passenger displays).
*
* <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
- * is stopped) and {@code DisplayManagerService} (when a virtual display is destroyed).
+ * is stopped).
*/
public abstract void unassignUserFromDisplay(@UserIdInt int userId);
@@ -390,4 +412,13 @@ public abstract class UserManagerInternal {
* would make such call).
*/
public abstract @UserIdInt int getUserAssignedToDisplay(int displayId);
+
+ /** Adds a {@link UserVisibilityListener}. */
+ public abstract void addUserVisibilityListener(UserVisibilityListener listener);
+
+ /** Removes a {@link UserVisibilityListener}. */
+ public abstract void removeUserVisibilityListener(UserVisibilityListener listener);
+
+ /** TODO(b/244333150): temporary method until UserVisibilityMediator handles that logic */
+ public abstract void onUserVisibilityChanged(@UserIdInt int userId, boolean visible);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0eff9e9a8e4d..d25566980fbb 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -96,6 +96,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.Slog;
@@ -124,9 +125,11 @@ import com.android.server.BundleUtils;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemService;
+import com.android.server.am.EventLogTags;
import com.android.server.am.UserState;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
+import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
import com.android.server.storage.DeviceStorageMonitorInternal;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -504,6 +507,10 @@ public class UserManagerService extends IUserManager.Stub {
@GuardedBy("mUserLifecycleListeners")
private final ArrayList<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>();
+ // TODO(b/244333150): temporary array, should belong to UserVisibilityMediator
+ @GuardedBy("mUserVisibilityListeners")
+ private final ArrayList<UserVisibilityListener> mUserVisibilityListeners = new ArrayList<>();
+
private final LockPatternUtils mLockPatternUtils;
private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
@@ -626,7 +633,7 @@ public class UserManagerService extends IUserManager.Stub {
@GuardedBy("mUserStates")
private final WatchedUserStates mUserStates = new WatchedUserStates();
- private final UserVisibilityMediator mUserVisibilityMediator;
+ private final UserVisibilityMediator mUserVisibilityMediator = new UserVisibilityMediator();
private static UserManagerService sInstance;
@@ -749,7 +756,6 @@ public class UserManagerService extends IUserManager.Stub {
mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING);
mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
emulateSystemUserModeIfNeeded();
- mUserVisibilityMediator = new UserVisibilityMediator(this);
}
void systemReady() {
@@ -1776,7 +1782,7 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
- public List<UserHandle> getVisibleUsers() {
+ public int[] getVisibleUsers() {
if (!hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) {
throw new SecurityException("Caller needs MANAGE_USERS or INTERACT_ACROSS_USERS "
+ "permission to get list of visible users");
@@ -1784,18 +1790,19 @@ public class UserManagerService extends IUserManager.Stub {
final long ident = Binder.clearCallingIdentity();
try {
// TODO(b/2399825580): refactor into UserDisplayAssigner
+ IntArray visibleUsers;
synchronized (mUsersLock) {
int usersSize = mUsers.size();
- ArrayList<UserHandle> visibleUsers = new ArrayList<>(usersSize);
+ visibleUsers = new IntArray();
for (int i = 0; i < usersSize; i++) {
UserInfo ui = mUsers.valueAt(i).info;
if (!ui.partial && !ui.preCreated && !mRemovingUserIds.get(ui.id)
&& mUserVisibilityMediator.isUserVisible(ui.id)) {
- visibleUsers.add(UserHandle.of(ui.id));
+ visibleUsers.add(ui.id);
}
}
- return visibleUsers;
}
+ return visibleUsers.toArray();
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -6147,7 +6154,7 @@ public class UserManagerService extends IUserManager.Stub {
dumpUser(pw, UserHandle.parseUserArg(args[1]), sb, now, nowRealtime);
return;
case "--visibility-mediator":
- mUserVisibilityMediator.dump(pw);
+ mUserVisibilityMediator.dump(pw, args);
return;
}
}
@@ -6213,7 +6220,7 @@ public class UserManagerService extends IUserManager.Stub {
} // synchronized (mPackagesLock)
pw.println();
- mUserVisibilityMediator.dump(pw);
+ mUserVisibilityMediator.dump(pw, args);
pw.println();
// Dump some capabilities
@@ -6250,6 +6257,9 @@ public class UserManagerService extends IUserManager.Stub {
synchronized (mUserLifecycleListeners) {
pw.println(" user lifecycle events: " + mUserLifecycleListeners.size());
}
+ synchronized (mUserVisibilityListeners) {
+ pw.println(" user visibility events: " + mUserVisibilityListeners.size());
+ }
// Dump UserTypes
pw.println();
@@ -6789,13 +6799,16 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
- public void assignUserToDisplay(@UserIdInt int userId, int displayId) {
- mUserVisibilityMediator.assignUserToDisplay(userId, displayId);
+ public void assignUserToDisplay(@UserIdInt int userId, @UserIdInt int profileGroupId,
+ boolean foreground, int displayId) {
+ mUserVisibilityMediator.startUser(userId, profileGroupId, foreground, displayId);
+ mUserVisibilityMediator.assignUserToDisplay(userId, profileGroupId, displayId);
}
@Override
public void unassignUserFromDisplay(@UserIdInt int userId) {
mUserVisibilityMediator.unassignUserFromDisplay(userId);
+ mUserVisibilityMediator.stopUser(userId);
}
@Override
@@ -6817,8 +6830,39 @@ public class UserManagerService extends IUserManager.Stub {
public @UserIdInt int getUserAssignedToDisplay(int displayId) {
return mUserVisibilityMediator.getUserAssignedToDisplay(displayId);
}
+
+ @Override
+ public void addUserVisibilityListener(UserVisibilityListener listener) {
+ synchronized (mUserVisibilityListeners) {
+ mUserVisibilityListeners.add(listener);
+ }
+ }
+
+ @Override
+ public void removeUserVisibilityListener(UserVisibilityListener listener) {
+ synchronized (mUserVisibilityListeners) {
+ mUserVisibilityListeners.remove(listener);
+ }
+ }
+
+ @Override
+ public void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
+ EventLog.writeEvent(EventLogTags.UM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
+ mHandler.post(() -> {
+ UserVisibilityListener[] listeners;
+ synchronized (mUserVisibilityListeners) {
+ listeners = new UserVisibilityListener[mUserVisibilityListeners.size()];
+ mUserVisibilityListeners.toArray(listeners);
+ }
+ for (UserVisibilityListener listener : listeners) {
+ listener.onUserVisibilityChanged(userId, visible);
+ }
+ });
+ }
} // class LocalService
+
+
/**
* Check if user has restrictions
* @param restriction restrictions to check
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index f725c486ec1f..bd81062b0ff1 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -15,10 +15,18 @@
*/
package com.android.server.pm;
+import static android.content.pm.UserInfo.NO_PROFILE_GROUP_ID;
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.DebugUtils;
+import android.util.Dumpable;
import android.util.IndentingPrintWriter;
import android.util.SparseIntArray;
import android.view.Display;
@@ -29,6 +37,8 @@ import com.android.internal.util.Preconditions;
import com.android.server.utils.Slogf;
import java.io.PrintWriter;
+import java.util.LinkedHashMap;
+import java.util.Map;
/**
* Class responsible for deciding whether a user is visible (or visible for a given display).
@@ -36,72 +46,146 @@ import java.io.PrintWriter;
* <p>This class is thread safe.
*/
// TODO(b/244644281): improve javadoc (for example, explain all cases / modes)
-public final class UserVisibilityMediator {
+public final class UserVisibilityMediator implements Dumpable {
private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
private static final String TAG = UserVisibilityMediator.class.getSimpleName();
- private final Object mLock = new Object();
+ private static final String PREFIX_START_USER_RESULT = "START_USER_";
+
+ // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
+ @VisibleForTesting
+ static final int INITIAL_CURRENT_USER_ID = USER_SYSTEM;
+
+ public static final int START_USER_RESULT_SUCCESS_VISIBLE = 1;
+ public static final int START_USER_RESULT_SUCCESS_INVISIBLE = 2;
+ public static final int START_USER_RESULT_FAILURE = -1;
- // TODO(b/244644281): should not depend on service, but keep its own internal state (like
- // current user and profile groups), but it is initially as the code was just moved from UMS
- // "as is". Similarly, it shouldn't need to pass the SparseIntArray on constructor (which was
- // added to UMS for testing purposes)
- private final UserManagerService mService;
+ @IntDef(flag = false, prefix = {PREFIX_START_USER_RESULT}, value = {
+ START_USER_RESULT_SUCCESS_VISIBLE,
+ START_USER_RESULT_SUCCESS_INVISIBLE,
+ START_USER_RESULT_FAILURE
+ })
+ public @interface StartUserResult {}
+
+ private final Object mLock = new Object();
private final boolean mUsersOnSecondaryDisplaysEnabled;
+ @UserIdInt
+ @GuardedBy("mLock")
+ private int mCurrentUserId = INITIAL_CURRENT_USER_ID;
+
@Nullable
@GuardedBy("mLock")
- private final SparseIntArray mUsersOnSecondaryDisplays;
+ private final SparseIntArray mUsersOnSecondaryDisplays = new SparseIntArray();
- UserVisibilityMediator(UserManagerService service) {
- this(service, UserManager.isUsersOnSecondaryDisplaysEnabled(),
- /* usersOnSecondaryDisplays= */ null);
+ /**
+ * Mapping from each started user to its profile group.
+ */
+ @GuardedBy("mLock")
+ private final SparseIntArray mStartedProfileGroupIds = new SparseIntArray();
+
+ UserVisibilityMediator() {
+ this(UserManager.isUsersOnSecondaryDisplaysEnabled());
}
@VisibleForTesting
- UserVisibilityMediator(UserManagerService service, boolean usersOnSecondaryDisplaysEnabled,
- @Nullable SparseIntArray usersOnSecondaryDisplays) {
- mService = service;
+ UserVisibilityMediator(boolean usersOnSecondaryDisplaysEnabled) {
mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled;
- if (mUsersOnSecondaryDisplaysEnabled) {
- mUsersOnSecondaryDisplays = usersOnSecondaryDisplays == null
- ? new SparseIntArray() // default behavior
- : usersOnSecondaryDisplays; // passed by unit test
- } else {
- mUsersOnSecondaryDisplays = null;
+ }
+
+ /**
+ * TODO(b/244644281): merge with assignUserToDisplay() or add javadoc.
+ */
+ public @StartUserResult int startUser(@UserIdInt int userId, @UserIdInt int profileGroupId,
+ boolean foreground, int displayId) {
+ int actualProfileGroupId = profileGroupId == NO_PROFILE_GROUP_ID
+ ? userId
+ : profileGroupId;
+ if (DBG) {
+ Slogf.d(TAG, "startUser(%d, %d, %b, %d): actualProfileGroupId=%d",
+ userId, profileGroupId, foreground, displayId, actualProfileGroupId);
+ }
+ if (foreground && displayId != DEFAULT_DISPLAY) {
+ Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start foreground user on "
+ + "secondary display", userId, actualProfileGroupId, foreground, displayId);
+ return START_USER_RESULT_FAILURE;
+ }
+
+ int visibility;
+ synchronized (mLock) {
+ if (isProfile(userId, actualProfileGroupId)) {
+ if (displayId != DEFAULT_DISPLAY) {
+ Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user on "
+ + "secondary display", userId, actualProfileGroupId, foreground,
+ displayId);
+ return START_USER_RESULT_FAILURE;
+ }
+ if (foreground) {
+ Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in "
+ + "foreground", userId, actualProfileGroupId, foreground,
+ displayId);
+ return START_USER_RESULT_FAILURE;
+ } else {
+ boolean isParentRunning = mStartedProfileGroupIds
+ .get(actualProfileGroupId) == actualProfileGroupId;
+ if (DBG) {
+ Slogf.d(TAG, "profile parent running: %b", isParentRunning);
+ }
+ visibility = isParentRunning
+ ? START_USER_RESULT_SUCCESS_VISIBLE
+ : START_USER_RESULT_SUCCESS_INVISIBLE;
+ }
+ } else if (foreground) {
+ mCurrentUserId = userId;
+ visibility = START_USER_RESULT_SUCCESS_VISIBLE;
+ } else {
+ visibility = START_USER_RESULT_SUCCESS_INVISIBLE;
+ }
+ if (DBG) {
+ Slogf.d(TAG, "adding user / profile mapping (%d -> %d) and returning %s",
+ userId, actualProfileGroupId, startUserResultToString(visibility));
+ }
+ mStartedProfileGroupIds.put(userId, actualProfileGroupId);
+ }
+ return visibility;
+ }
+
+ /**
+ * TODO(b/244644281): merge with unassignUserFromDisplay() or add javadoc (and unit tests)
+ */
+ public void stopUser(@UserIdInt int userId) {
+ if (DBG) {
+ Slogf.d(TAG, "stopUser(%d)", userId);
+ }
+ synchronized (mLock) {
+ mStartedProfileGroupIds.delete(userId);
}
}
/**
* See {@link UserManagerInternal#assignUserToDisplay(int, int)}.
*/
- public void assignUserToDisplay(int userId, int displayId) {
+ public void assignUserToDisplay(int userId, int profileGroupId, int displayId) {
if (DBG) {
- Slogf.d(TAG, "assignUserToDisplay(%d, %d)", userId, displayId);
+ Slogf.d(TAG, "assignUserToDisplay(%d, %d): mUsersOnSecondaryDisplaysEnabled=%b",
+ userId, displayId, mUsersOnSecondaryDisplaysEnabled);
}
- // NOTE: Using Boolean instead of boolean as it will be re-used below
- Boolean isProfile = null;
- if (displayId == Display.DEFAULT_DISPLAY) {
- if (mUsersOnSecondaryDisplaysEnabled) {
- // Profiles are only supported in the default display, but it cannot return yet
- // as it needs to check if the parent is also assigned to the DEFAULT_DISPLAY
- // (this is done indirectly below when it checks that the profile parent is the
- // current user, as the current user is always assigned to the DEFAULT_DISPLAY).
- isProfile = isProfileUnchecked(userId);
- }
- if (isProfile == null || !isProfile) {
- // Don't need to do anything because methods (such as isUserVisible()) already
- // know that the current user (and their profiles) is assigned to the default
- // display.
- if (DBG) {
- Slogf.d(TAG, "ignoring on default display");
- }
- return;
+ if (displayId == DEFAULT_DISPLAY
+ && (!mUsersOnSecondaryDisplaysEnabled || !isProfile(userId, profileGroupId))) {
+ // Don't need to do anything because methods (such as isUserVisible()) already
+ // know that the current user (and their profiles) is assigned to the default display.
+ // But on MUMD devices, it profiles are only supported in the default display, so it
+ // cannot return yet as it needs to check if the parent is also assigned to the
+ // DEFAULT_DISPLAY (this is done indirectly below when it checks that the profile parent
+ // is the current user, as the current user is always assigned to the DEFAULT_DISPLAY).
+ if (DBG) {
+ Slogf.d(TAG, "ignoring on default display");
}
+ return;
}
if (!mUsersOnSecondaryDisplaysEnabled) {
@@ -119,24 +203,21 @@ public final class UserVisibilityMediator {
Preconditions.checkArgument(userId != currentUserId,
"Cannot assign current user (%d) to other displays", currentUserId);
- if (isProfile == null) {
- isProfile = isProfileUnchecked(userId);
- }
- synchronized (mLock) {
- if (isProfile) {
- // Profile can only start in the same display as parent. And for simplicity,
- // that display must be the DEFAULT_DISPLAY.
- Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
- "Profile user can only be started in the default display");
- int parentUserId = getProfileParentId(userId);
- Preconditions.checkArgument(parentUserId == currentUserId,
- "Only profile of current user can be assigned to a display");
- if (DBG) {
- Slogf.d(TAG, "Ignoring profile user %d on default display", userId);
- }
- return;
+ if (isProfile(userId, profileGroupId)) {
+ // Profile can only start in the same display as parent. And for simplicity,
+ // that display must be the DEFAULT_DISPLAY.
+ Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
+ "Profile user can only be started in the default display");
+ int parentUserId = getStartedProfileGroupId(userId);
+ Preconditions.checkArgument(parentUserId == currentUserId,
+ "Only profile of current user can be assigned to a display");
+ if (DBG) {
+ Slogf.d(TAG, "Ignoring profile user %d on default display", userId);
}
+ return;
+ }
+ synchronized (mLock) {
// Check if display is available
for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i);
@@ -289,7 +370,7 @@ public final class UserVisibilityMediator {
continue;
}
int userId = mUsersOnSecondaryDisplays.keyAt(i);
- if (!isProfileUnchecked(userId)) {
+ if (!isStartedProfile(userId)) {
return userId;
} else if (DBG) {
Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
@@ -307,23 +388,42 @@ public final class UserVisibilityMediator {
}
private void dump(IndentingPrintWriter ipw) {
- ipw.println("UserVisibilityManager");
+ ipw.println("UserVisibilityMediator");
ipw.increaseIndent();
- ipw.print("Supports users on secondary displays: ");
- ipw.println(mUsersOnSecondaryDisplaysEnabled);
+ synchronized (mLock) {
+ ipw.print("Current user id: ");
+ ipw.println(mCurrentUserId);
+
+ ipw.print("Number of started user / profile group mappings: ");
+ ipw.println(mStartedProfileGroupIds.size());
+ if (mStartedProfileGroupIds.size() > 0) {
+ ipw.increaseIndent();
+ for (int i = 0; i < mStartedProfileGroupIds.size(); i++) {
+ ipw.print("User #");
+ ipw.print(mStartedProfileGroupIds.keyAt(i));
+ ipw.print(" -> profile #");
+ ipw.println(mStartedProfileGroupIds.valueAt(i));
+ }
+ ipw.decreaseIndent();
+ }
- if (mUsersOnSecondaryDisplaysEnabled) {
- ipw.print("Users on secondary displays: ");
- synchronized (mLock) {
- ipw.println(mUsersOnSecondaryDisplays);
+ ipw.print("Supports users on secondary displays: ");
+ ipw.println(mUsersOnSecondaryDisplaysEnabled);
+
+ if (mUsersOnSecondaryDisplaysEnabled) {
+ ipw.print("Users on secondary displays: ");
+ synchronized (mLock) {
+ ipw.println(mUsersOnSecondaryDisplays);
+ }
}
}
ipw.decreaseIndent();
}
- void dump(PrintWriter pw) {
+ @Override
+ public void dump(PrintWriter pw, String[] args) {
if (pw instanceof IndentingPrintWriter) {
dump((IndentingPrintWriter) pw);
return;
@@ -331,20 +431,70 @@ public final class UserVisibilityMediator {
dump(new IndentingPrintWriter(pw));
}
- // TODO(b/244644281): remove methods below once this class caches that state
- private @UserIdInt int getCurrentUserId() {
- return mService.getCurrentUserId();
+ @VisibleForTesting
+ Map<Integer, Integer> getUsersOnSecondaryDisplays() {
+ Map<Integer, Integer> map;
+ synchronized (mLock) {
+ int size = mUsersOnSecondaryDisplays.size();
+ map = new LinkedHashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ map.put(mUsersOnSecondaryDisplays.keyAt(i), mUsersOnSecondaryDisplays.valueAt(i));
+ }
+ }
+ Slogf.v(TAG, "getUsersOnSecondaryDisplays(): returning %s", map);
+ return map;
+ }
+
+ /**
+ * Gets the user-friendly representation of the {@code result}.
+ */
+ public static String startUserResultToString(@StartUserResult int result) {
+ return DebugUtils.constantToString(UserVisibilityMediator.class, PREFIX_START_USER_RESULT,
+ result);
}
- private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
- return mService.isCurrentUserOrRunningProfileOfCurrentUser(userId);
+ // TODO(b/244644281): methods below are needed because some APIs use the current users (full and
+ // profiles) state to decide whether a user is visible or not. If we decide to always store that
+ // info into intermediate maps, we should remove them.
+
+ @VisibleForTesting
+ @UserIdInt int getCurrentUserId() {
+ synchronized (mLock) {
+ return mCurrentUserId;
+ }
}
- private boolean isProfileUnchecked(@UserIdInt int userId) {
- return mService.isProfileUnchecked(userId);
+ @VisibleForTesting
+ boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ // Special case as NO_PROFILE_GROUP_ID == USER_NULL
+ if (userId == USER_NULL || mCurrentUserId == USER_NULL) {
+ return false;
+ }
+ if (mCurrentUserId == userId) {
+ return true;
+ }
+ return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID) == mCurrentUserId;
+ }
}
- private @UserIdInt int getProfileParentId(@UserIdInt int userId) {
- return mService.getProfileParentId(userId);
+ private static boolean isProfile(@UserIdInt int userId, @UserIdInt int profileGroupId) {
+ return profileGroupId != NO_PROFILE_GROUP_ID && profileGroupId != userId;
+ }
+
+ @VisibleForTesting
+ boolean isStartedProfile(@UserIdInt int userId) {
+ int profileGroupId;
+ synchronized (mLock) {
+ profileGroupId = mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+ }
+ return isProfile(userId, profileGroupId);
+ }
+
+ @VisibleForTesting
+ @UserIdInt int getStartedProfileGroupId(@UserIdInt int userId) {
+ synchronized (mLock) {
+ return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID);
+ }
}
}
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
index 8582f5424321..2d76c5092b92 100644
--- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -127,7 +127,7 @@ public class SideFpsEventHandler implements View.OnClickListener {
*/
public void notifyPowerPressed() {
Log.i(TAG, "notifyPowerPressed");
- if (mFingerprintManager == null) {
+ if (mFingerprintManager == null && mSideFpsEventHandlerReady.get()) {
mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
}
if (mFingerprintManager == null) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 7dc4f9782e70..b8cd8d9f3d71 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1157,6 +1157,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Slog.w(TAG, "WallpaperService is not connected yet");
return;
}
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent);
if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
null /* options */);
@@ -1173,6 +1175,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
false /* fromUser */, wallpaper, null /* reply */);
}
}
+ t.traceEnd();
}
void disconnectLocked() {
@@ -1322,6 +1325,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.onServiceConnected-" + name);
synchronized (mLock) {
if (mWallpaper.connection == this) {
mService = IWallpaperService.Stub.asInterface(service);
@@ -1338,6 +1343,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
mContext.getMainThreadHandler().removeCallbacks(mDisconnectRunnable);
}
}
+ t.traceEnd();
}
@Override
@@ -1545,6 +1551,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
public void engineShown(IWallpaperEngine engine) {
synchronized (mLock) {
if (mReply != null) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.mReply.sendResult");
final long ident = Binder.clearCallingIdentity();
try {
mReply.sendResult(null);
@@ -1553,6 +1561,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
} finally {
Binder.restoreCallingIdentity(ident);
}
+ t.traceEnd();
mReply = null;
}
}
@@ -3058,6 +3067,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return true;
}
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.bindWallpaperComponentLocked-" + componentName);
try {
if (componentName == null) {
componentName = mDefaultWallpaperComponent;
@@ -3190,6 +3201,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
Slog.w(TAG, msg);
return false;
+ } finally {
+ t.traceEnd();
}
return true;
}
@@ -3234,7 +3247,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
private void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.attachServiceLocked");
conn.forEachDisplayConnector(connector-> connector.connectLocked(conn, wallpaper));
+ t.traceEnd();
}
private void notifyCallbacksLocked(WallpaperData wallpaper) {
@@ -3360,6 +3376,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
void saveSettingsLocked(int userId) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.saveSettingsLocked-" + userId);
JournaledFile journal = makeJournaledFile(userId);
FileOutputStream fstream = null;
try {
@@ -3388,6 +3406,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
IoUtils.closeQuietly(fstream);
journal.rollback();
}
+ t.traceEnd();
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index d0c381e9debc..21b241a0d117 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -56,8 +56,11 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener {
private static final int SURFACE_FLINGER_CALLBACK_WINDOWS_STABLE_TIMES_MS = 35;
// To avoid the surface flinger callbacks always comes within in 2 frames, then no windows
// are reported to the A11y framework, and the animation duration time is 500ms, so setting
- // this value as the max timeout value to force computing changed windows.
- private static final int WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS = 500;
+ // this value as the max timeout value to force computing changed windows. However, since
+ // UiAutomator waits 500ms to determine that things are idle. Since we aren't actually idle,
+ // we need to reduce the timeout here a little so that we can deliver an updated state before
+ // UiAutomator reports idle based-on stale information.
+ private static final int WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS = 450;
private static final float[] sTempFloats = new float[9];
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 59f37c27f637..7386a19009e0 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -86,6 +86,7 @@ import android.window.TransitionInfo;
import com.android.internal.app.AssistUtils;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
import com.android.server.pm.KnownPackages;
@@ -454,6 +455,39 @@ class ActivityClientController extends IActivityClientController.Stub {
finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
|| (finishWithRootActivity && r == rootR)) {
+ ActivityRecord topActivity =
+ r.getTask().getTopNonFinishingActivity();
+ boolean passesAsmChecks = topActivity != null
+ && topActivity.getUid() == r.getUid();
+ if (!passesAsmChecks) {
+ Slog.i(TAG, "Finishing task from background. r: " + r);
+ FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
+ /* caller_uid */
+ r.getUid(),
+ /* caller_activity_class_name */
+ r.info.name,
+ /* target_task_top_activity_uid */
+ topActivity == null ? -1 : topActivity.getUid(),
+ /* target_task_top_activity_class_name */
+ topActivity == null ? null : topActivity.info.name,
+ /* target_task_is_different */
+ false,
+ /* target_activity_uid */
+ -1,
+ /* target_activity_class_name */
+ null,
+ /* target_intent_action */
+ null,
+ /* target_intent_flags */
+ 0,
+ /* action */
+ FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
+ /* version */
+ 1,
+ /* multi_window */
+ false
+ );
+ }
// If requested, remove the task that is associated to this activity only if it
// was the root activity in the task. The result code and data is ignored
// because we don't support returning them across task boundaries. Also, to
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0eee8a329a1e..2a3946f77847 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -9204,10 +9204,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
+ " preserveWindow=" + preserveWindow);
if (andResume) {
EventLogTags.writeWmRelaunchResumeActivity(mUserId, System.identityHashCode(this),
- task.mTaskId, shortComponentName);
+ task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
} else {
EventLogTags.writeWmRelaunchActivity(mUserId, System.identityHashCode(this),
- task.mTaskId, shortComponentName);
+ task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
}
startFreezingScreenLocked(0);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index dc047aaab31c..e6d94920c00e 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1663,7 +1663,8 @@ class ActivityStarter {
}
final Task startedTask = mStartActivity.getTask();
if (newTask) {
- EventLogTags.writeWmCreateTask(mStartActivity.mUserId, startedTask.mTaskId);
+ EventLogTags.writeWmCreateTask(mStartActivity.mUserId, startedTask.mTaskId,
+ startedTask.getRootTaskId(), startedTask.getDisplayId());
}
mStartActivity.logStartActivity(EventLogTags.WM_CREATE_ACTIVITY, startedTask);
@@ -1856,6 +1857,11 @@ class ActivityStarter {
+ " from background: " + mSourceRecord
+ ". New task: " + newTask);
boolean newOrEmptyTask = newTask || (targetTopActivity == null);
+ int action = newTask
+ ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_NEW_TASK
+ : (mSourceRecord.getTask().equals(targetTask)
+ ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_SAME_TASK
+ : FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_DIFFERENT_TASK);
FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
/* caller_uid */
callerUid,
@@ -1874,7 +1880,14 @@ class ActivityStarter {
/* target_intent_action */
r.intent.getAction(),
/* target_intent_flags */
- r.intent.getFlags()
+ r.intent.getFlags(),
+ /* action */
+ action,
+ /* version */
+ 1,
+ /* multi_window */
+ targetTask != null && !targetTask.equals(mSourceRecord.getTask())
+ && targetTask.isVisible()
);
}
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 901153300169..bd22b32742a1 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -215,9 +215,20 @@ public class AppTransitionController {
mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(
mDisplayContent.mOpeningApps);
+ ArraySet<ActivityRecord> tmpOpenApps = mDisplayContent.mOpeningApps;
+ ArraySet<ActivityRecord> tmpCloseApps = mDisplayContent.mClosingApps;
+ if (mDisplayContent.mAtmService.mBackNavigationController.isWaitBackTransition()) {
+ tmpOpenApps = new ArraySet<>(mDisplayContent.mOpeningApps);
+ tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps);
+ if (mDisplayContent.mAtmService.mBackNavigationController
+ .removeIfContainsBackAnimationTargets(tmpOpenApps, tmpCloseApps)) {
+ mDisplayContent.mAtmService.mBackNavigationController.clearBackAnimations(null);
+ }
+ }
+
@TransitionOldType final int transit = getTransitCompatType(
- mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps,
- mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers,
+ mDisplayContent.mAppTransition, tmpOpenApps,
+ tmpCloseApps, mDisplayContent.mChangingContainers,
mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),
mDisplayContent.mSkipAppTransitionAnimation);
mDisplayContent.mSkipAppTransitionAnimation = false;
@@ -225,22 +236,21 @@ public class AppTransitionController {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"handleAppTransitionReady: displayId=%d appTransition={%s}"
+ " openingApps=[%s] closingApps=[%s] transit=%s",
- mDisplayContent.mDisplayId, appTransition.toString(), mDisplayContent.mOpeningApps,
- mDisplayContent.mClosingApps, AppTransition.appTransitionOldToString(transit));
+ mDisplayContent.mDisplayId, appTransition.toString(), tmpOpenApps,
+ tmpCloseApps, AppTransition.appTransitionOldToString(transit));
// Find the layout params of the top-most application window in the tokens, which is
// what will control the animation theme. If all closing windows are obscured, then there is
// no need to do an animation. This is the case, for example, when this transition is being
// done behind a dream window.
- final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps,
- mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers);
+ final ArraySet<Integer> activityTypes = collectActivityTypes(tmpOpenApps,
+ tmpCloseApps, mDisplayContent.mChangingContainers);
final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- mDisplayContent.mChangingContainers);
+ tmpOpenApps, tmpCloseApps, mDisplayContent.mChangingContainers);
final ActivityRecord topOpeningApp =
- getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */);
+ getTopApp(tmpOpenApps, false /* ignoreHidden */);
final ActivityRecord topClosingApp =
- getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */);
+ getTopApp(tmpCloseApps, false /* ignoreHidden */);
final ActivityRecord topChangingApp =
getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
@@ -258,8 +268,7 @@ public class AppTransitionController {
final int layoutRedo;
mService.mSurfaceAnimationRunner.deferStartingAnimations();
try {
- applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit,
- animLp, voiceInteraction);
+ applyAnimations(tmpOpenApps, tmpCloseApps, transit, animLp, voiceInteraction);
handleClosingApps();
handleOpeningApps();
handleChangingApps(transit);
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 6ff2bb30cc0c..9398bbec4a63 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -19,6 +19,8 @@ package com.android.server.wm;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
@@ -32,6 +34,7 @@ import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.util.ArraySet;
import android.util.Slog;
import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationTarget;
@@ -41,7 +44,6 @@ import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
import android.window.OnBackInvokedCallbackInfo;
import android.window.TaskSnapshot;
-import android.window.WindowContainerToken;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -60,9 +62,9 @@ class BackNavigationController {
private boolean mShowWallpaper;
private Runnable mPendingAnimation;
- // TODO (b/241808055) Find a appropriate time to remove during refactor
- // Execute back animation with legacy transition system. Temporary flag for easier debugging.
- static final boolean ENABLE_SHELL_TRANSITIONS = WindowManagerService.sEnableShellTransitions;
+ private final AnimationTargets mAnimationTargets = new AnimationTargets();
+ private final ArrayList<WindowContainer> mTmpOpenApps = new ArrayList<>();
+ private final ArrayList<WindowContainer> mTmpCloseApps = new ArrayList<>();
/**
* true if the back predictability feature is enabled
@@ -284,7 +286,6 @@ class BackNavigationController {
}
if (prepareAnimation) {
- infoBuilder.setDepartingWCT(toWindowContainerToken(currentTask));
prepareAnimationIfNeeded(currentTask, prevTask, prevActivity,
removedWindowContainer, backType, adapter);
}
@@ -303,11 +304,233 @@ class BackNavigationController {
return infoBuilder.build();
}
- private static WindowContainerToken toWindowContainerToken(WindowContainer<?> windowContainer) {
- if (windowContainer == null || windowContainer.mRemoteToken == null) {
- return null;
+ boolean isWaitBackTransition() {
+ return mAnimationTargets.mComposed && mAnimationTargets.mWaitTransition;
+ }
+
+ // For legacy transition.
+ /**
+ * Once we find the transition targets match back animation targets, remove the target from
+ * list, so that transition won't count them in since the close animation was finished.
+ *
+ * @return {@code true} if the participants of this transition was animated by back gesture
+ * animations, and shouldn't join next transition.
+ */
+ boolean removeIfContainsBackAnimationTargets(ArraySet<ActivityRecord> openApps,
+ ArraySet<ActivityRecord> closeApps) {
+ if (!isWaitBackTransition()) {
+ return false;
+ }
+ mTmpCloseApps.addAll(closeApps);
+ boolean result = false;
+ // Note: TmpOpenApps is empty. Unlike shell transition, the open apps will be removed from
+ // mOpeningApps if there is no visibility change.
+ if (mAnimationTargets.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps)) {
+ // remove close target from close list, open target from open list;
+ // but the open target can be in close list.
+ for (int i = openApps.size() - 1; i >= 0; --i) {
+ final ActivityRecord ar = openApps.valueAt(i);
+ if (mAnimationTargets.isTarget(ar, true /* open */)) {
+ openApps.removeAt(i);
+ }
+ }
+ for (int i = closeApps.size() - 1; i >= 0; --i) {
+ final ActivityRecord ar = closeApps.valueAt(i);
+ if (mAnimationTargets.isTarget(ar, false /* open */)) {
+ closeApps.removeAt(i);
+ }
+ }
+ result = true;
+ }
+ mTmpCloseApps.clear();
+ return result;
+ }
+
+ // For shell transition
+ /**
+ * Check whether the transition targets was animated by back gesture animation.
+ * Because the opening target could request to do other stuff at onResume, so it could become
+ * close target for a transition. So the condition here is
+ * The closing target should only exist in close list, but the opening target can be either in
+ * open or close list.
+ * @return {@code true} if the participants of this transition was animated by back gesture
+ * animations, and shouldn't join next transition.
+ */
+ boolean containsBackAnimationTargets(Transition transition) {
+ if (!mAnimationTargets.mComposed
+ || (transition.mType != TRANSIT_CLOSE && transition.mType != TRANSIT_TO_BACK)) {
+ return false;
+ }
+ final ArraySet<WindowContainer> targets = transition.mParticipants;
+ for (int i = targets.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = targets.valueAt(i);
+ if (wc.asActivityRecord() == null && wc.asTask() == null) {
+ continue;
+ }
+ // WC can be visible due to setLaunchBehind
+ if (wc.isVisibleRequested()) {
+ mTmpOpenApps.add(wc);
+ } else {
+ mTmpCloseApps.add(wc);
+ }
+ }
+ final boolean result = mAnimationTargets.containsBackAnimationTargets(
+ mTmpOpenApps, mTmpCloseApps);
+ mTmpOpenApps.clear();
+ mTmpCloseApps.clear();
+ return result;
+ }
+
+ boolean isMonitorTransitionTarget(WindowContainer wc) {
+ if (!mAnimationTargets.mComposed || !mAnimationTargets.mWaitTransition) {
+ return false;
+ }
+ return mAnimationTargets.isTarget(wc, wc.isVisibleRequested() /* open */);
+ }
+
+ /**
+ * Cleanup animation, this can either happen when transition ready or finish.
+ * @param cleanupTransaction The transaction which the caller want to apply the internal
+ * cleanup together.
+ */
+ void clearBackAnimations(SurfaceControl.Transaction cleanupTransaction) {
+ mAnimationTargets.clearBackAnimateTarget(cleanupTransaction);
+ }
+
+ /**
+ * TODO: Animation composer
+ * prepareAnimationIfNeeded will become too complicated in order to support
+ * ActivityRecord/WindowState, using a factory class to create the RemoteAnimationTargets for
+ * different scenario.
+ */
+ private static class AnimationTargets {
+ ActivityRecord mCloseTarget; // Must be activity
+ WindowContainer mOpenTarget; // Can be activity or task if activity was removed
+ private boolean mComposed;
+ private boolean mWaitTransition;
+ private int mSwitchType = UNKNOWN;
+ private SurfaceControl.Transaction mFinishedTransaction;
+
+ private static final int UNKNOWN = 0;
+ private static final int TASK_SWITCH = 1;
+ private static final int ACTIVITY_SWITCH = 2;
+
+ void reset(@NonNull WindowContainer close, @NonNull WindowContainer open) {
+ clearBackAnimateTarget(null);
+ if (close.asActivityRecord() != null && open.asActivityRecord() != null
+ && (close.asActivityRecord().getTask() == open.asActivityRecord().getTask())) {
+ mSwitchType = ACTIVITY_SWITCH;
+ mCloseTarget = close.asActivityRecord();
+ } else if (close.asTask() != null && open.asTask() != null
+ && close.asTask() != open.asTask()) {
+ mSwitchType = TASK_SWITCH;
+ mCloseTarget = close.asTask().getTopNonFinishingActivity();
+ } else {
+ mSwitchType = UNKNOWN;
+ return;
+ }
+
+ mOpenTarget = open;
+ mComposed = false;
+ mWaitTransition = false;
+ }
+
+ void composeNewAnimations(@NonNull WindowContainer close, @NonNull WindowContainer open) {
+ reset(close, open);
+ if (mSwitchType == UNKNOWN || mComposed || mCloseTarget == mOpenTarget
+ || mCloseTarget == null || mOpenTarget == null) {
+ return;
+ }
+ mComposed = true;
+ mWaitTransition = false;
+ }
+
+ boolean containTarget(ArrayList<WindowContainer> wcs, boolean open) {
+ for (int i = wcs.size() - 1; i >= 0; --i) {
+ if (isTarget(wcs.get(i), open)) {
+ return true;
+ }
+ }
+ return wcs.isEmpty();
+ }
+
+ boolean isTarget(WindowContainer wc, boolean open) {
+ if (open) {
+ return wc == mOpenTarget || mOpenTarget.hasChild(wc);
+ }
+ if (mSwitchType == TASK_SWITCH) {
+ return wc == mCloseTarget
+ || (wc.asTask() != null && wc.hasChild(mCloseTarget));
+ } else if (mSwitchType == ACTIVITY_SWITCH) {
+ return wc == mCloseTarget;
+ }
+ return false;
+ }
+
+ boolean setFinishTransaction(SurfaceControl.Transaction finishTransaction) {
+ if (!mComposed) {
+ return false;
+ }
+ mFinishedTransaction = finishTransaction;
+ return true;
+ }
+
+ void finishPresentAnimations(SurfaceControl.Transaction t) {
+ if (!mComposed) {
+ return;
+ }
+ final SurfaceControl.Transaction pt = t != null ? t
+ : mOpenTarget.getPendingTransaction();
+ if (mFinishedTransaction != null) {
+ pt.merge(mFinishedTransaction);
+ mFinishedTransaction = null;
+ }
+ }
+
+ void clearBackAnimateTarget(SurfaceControl.Transaction cleanupTransaction) {
+ finishPresentAnimations(cleanupTransaction);
+ mCloseTarget = null;
+ mOpenTarget = null;
+ mComposed = false;
+ mWaitTransition = false;
+ mSwitchType = UNKNOWN;
+ if (mFinishedTransaction != null) {
+ Slog.w(TAG, "Clear back animation, found un-processed finished transaction");
+ if (cleanupTransaction != null) {
+ cleanupTransaction.merge(mFinishedTransaction);
+ } else {
+ mFinishedTransaction.apply();
+ }
+ mFinishedTransaction = null;
+ }
+ }
+
+ // The close target must in close list
+ // The open target can either in close or open list
+ boolean containsBackAnimationTargets(ArrayList<WindowContainer> openApps,
+ ArrayList<WindowContainer> closeApps) {
+ return containTarget(closeApps, false /* open */)
+ && (containTarget(openApps, true /* open */)
+ || containTarget(openApps, false /* open */));
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder(128);
+ sb.append("AnimationTargets{");
+ sb.append(" mOpenTarget= ");
+ sb.append(mOpenTarget);
+ sb.append(" mCloseTarget= ");
+ sb.append(mCloseTarget);
+ sb.append(" mSwitchType= ");
+ sb.append(mSwitchType);
+ sb.append(" mComposed= ");
+ sb.append(mComposed);
+ sb.append(" mWaitTransition= ");
+ sb.append(mWaitTransition);
+ sb.append('}');
+ return sb.toString();
}
- return windowContainer.mRemoteToken.toWindowContainerToken();
}
private void prepareAnimationIfNeeded(Task currentTask,
@@ -373,10 +596,6 @@ class BackNavigationController {
leashes.add(screenshotSurface);
}
} else if (prevTask != null) {
- if (!ENABLE_SHELL_TRANSITIONS) {
- // Special handling for preventing next transition.
- currentTask.mBackGestureStarted = true;
- }
prevActivity = prevTask.getTopNonFinishingActivity();
if (prevActivity != null) {
// Make previous task show from behind by marking its top activity as visible
@@ -417,35 +636,36 @@ class BackNavigationController {
for (SurfaceControl sc: leashes) {
finishedTransaction.remove(sc);
}
-
synchronized (mWindowManagerService.mGlobalLock) {
- if (ENABLE_SHELL_TRANSITIONS) {
- if (!triggerBack) {
- if (!needsScreenshot(backType)) {
- restoreLaunchBehind(finalPrevActivity);
- }
+ if (triggerBack) {
+ final SurfaceControl surfaceControl =
+ removedWindowContainer.getSurfaceControl();
+ if (surfaceControl != null && surfaceControl.isValid()) {
+ // The animation is finish and start waiting for transition,
+ // hide the task surface before it re-parented to avoid flicker.
+ finishedTransaction.hide(surfaceControl);
}
+ } else if (!needsScreenshot(backType)) {
+ restoreLaunchBehind(finalPrevActivity);
+ }
+ if (!mAnimationTargets.setFinishTransaction(finishedTransaction)) {
+ finishedTransaction.apply();
+ }
+ if (!triggerBack) {
+ mAnimationTargets.clearBackAnimateTarget(null);
} else {
- if (triggerBack) {
- final SurfaceControl surfaceControl =
- removedWindowContainer.getSurfaceControl();
- if (surfaceControl != null && surfaceControl.isValid()) {
- // When going back to home, hide the task surface before it
- // re-parented to avoid flicker.
- finishedTransaction.hide(surfaceControl);
- }
- } else {
- currentTask.mBackGestureStarted = false;
- if (!needsScreenshot(backType)) {
- restoreLaunchBehind(finalPrevActivity);
- }
- }
+ mAnimationTargets.mWaitTransition = true;
}
}
- finishedTransaction.apply();
+ // TODO Add timeout monitor if transition didn't happen
}
};
-
+ if (backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
+ mAnimationTargets.composeNewAnimations(removedWindowContainer, prevActivity);
+ } else if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+ || backType == BackNavigationInfo.TYPE_CROSS_TASK) {
+ mAnimationTargets.composeNewAnimations(removedWindowContainer, prevTask);
+ }
scheduleAnimationLocked(backType, targets, adapter, callback);
}
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index 1e5a219e5e52..d94bf4bbbfa4 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -8,11 +8,11 @@ option java_package com.android.server.wm
# An activity is being finished:
30001 wm_finish_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
# A task is being brought to the front of the screen:
-30002 wm_task_to_front (User|1|5),(Task|1|5)
+30002 wm_task_to_front (User|1|5),(Task|1|5),(Display Id|1|5)
# An existing activity is being given a new intent:
30003 wm_new_intent (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
# A new task is being created:
-30004 wm_create_task (User|1|5),(Task ID|1|5)
+30004 wm_create_task (User|1|5),(Task ID|1|5),(Root Task ID|1|5),(Display Id|1|5)
# A new activity is being created in an existing task:
30005 wm_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
# An activity has been resumed into the foreground but was not already running:
@@ -32,9 +32,9 @@ option java_package com.android.server.wm
# An activity is being destroyed:
30018 wm_destroy_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
# An activity has been relaunched, resumed, and is now in the foreground:
-30019 wm_relaunch_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
+30019 wm_relaunch_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(config mask|3)
# An activity has been relaunched:
-30020 wm_relaunch_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
+30020 wm_relaunch_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(config mask|3)
# Activity set to resumed
30043 wm_set_resumed_activity (User|1|5),(Component Name|3),(Reason|3)
@@ -45,9 +45,6 @@ option java_package com.android.server.wm
# Attempting to stop an activity
30048 wm_stop_activity (User|1|5),(Token|1|5),(Component Name|3)
-# The task is being removed from its parent task
-30061 wm_remove_task (Task ID|1|5), (Root Task ID|1|5)
-
# An activity been add into stopping list
30066 wm_add_to_stopping (User|1|5),(Token|1|5),(Component Name|3),(Reason|3)
@@ -57,11 +54,11 @@ option java_package com.android.server.wm
# Out of memory for surfaces.
31000 wm_no_surface_memory (Window|3),(PID|1|5),(Operation|3)
# Task created.
-31001 wm_task_created (TaskId|1|5),(RootTaskId|1|5)
+31001 wm_task_created (TaskId|1|5)
# Task moved to top (1) or bottom (0).
-31002 wm_task_moved (TaskId|1|5),(ToTop|1),(Index|1)
+31002 wm_task_moved (TaskId|1|5),(Root Task ID|1|5),(Display Id|1|5),(ToTop|1),(Index|1)
# Task removed with source explanation.
-31003 wm_task_removed (TaskId|1|5),(Reason|3)
+31003 wm_task_removed (TaskId|1|5),(Root Task ID|1|5),(Display Id|1|5),(Reason|3)
# bootanim finished:
31007 wm_boot_animation_done (time|2|3)
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index ea82417a2389..74a236bd862c 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -501,12 +501,16 @@ final class LetterboxUiController {
if (hasVisibleTaskbar(mainWindow)) {
cropBounds = new Rect(mActivityRecord.getBounds());
+
+ // Rounded corners should be displayed above the taskbar.
+ // It is important to call adjustBoundsForTaskbarUnchecked before offsetTo
+ // because taskbar bounds are in screen coordinates
+ adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds);
+
// Activity bounds are in screen coordinates while (0,0) for activity's surface
// control is at the top left corner of an app window so offsetting bounds
// accordingly.
cropBounds.offsetTo(0, 0);
- // Rounded corners should be displayed above the taskbar.
- adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds);
}
transaction
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d06f2716b84a..cdb332123fe2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -67,7 +67,6 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
@@ -607,12 +606,6 @@ class Task extends TaskFragment {
boolean mLastSurfaceShowing = true;
- /**
- * Tracks if a back gesture is in progress.
- * Skips any system transition animations if this is set to {@code true}.
- */
- boolean mBackGestureStarted = false;
-
private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
Intent _affinityIntent, String _affinity, String _rootAffinity,
ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
@@ -680,7 +673,7 @@ class Task extends TaskFragment {
mLaunchCookie = _launchCookie;
mDeferTaskAppear = _deferTaskAppear;
mRemoveWithTaskOrganizer = _removeWithTaskOrganizer;
- EventLogTags.writeWmTaskCreated(mTaskId, isRootTask() ? INVALID_TASK_ID : getRootTaskId());
+ EventLogTags.writeWmTaskCreated(mTaskId);
}
static Task fromWindowContainerToken(WindowContainerToken token) {
@@ -1297,7 +1290,8 @@ class Task extends TaskFragment {
}
void updateTaskMovement(boolean toTop, int position) {
- EventLogTags.writeWmTaskMoved(mTaskId, toTop ? 1 : 0, position);
+ EventLogTags.writeWmTaskMoved(mTaskId, getRootTaskId(), getDisplayId(), toTop ? 1 : 0,
+ position);
final TaskDisplayArea taskDisplayArea = getDisplayArea();
if (taskDisplayArea != null && isLeafTask()) {
taskDisplayArea.onLeafTaskMoved(this, toTop);
@@ -2560,7 +2554,7 @@ class Task extends TaskFragment {
}
mRemoving = true;
- EventLogTags.writeWmTaskRemoved(mTaskId, reason);
+ EventLogTags.writeWmTaskRemoved(mTaskId, getRootTaskId(), getDisplayId(), reason);
clearPinnedTaskIfNeed();
// If applicable let the TaskOrganizer know the Task is vanishing.
setTaskOrganizer(null);
@@ -2573,7 +2567,8 @@ class Task extends TaskFragment {
void reparent(Task rootTask, int position, boolean moveParents, String reason) {
if (DEBUG_ROOT_TASK) Slog.i(TAG, "reParentTask: removing taskId=" + mTaskId
+ " from rootTask=" + getRootTask());
- EventLogTags.writeWmTaskRemoved(mTaskId, "reParentTask:" + reason);
+ EventLogTags.writeWmTaskRemoved(mTaskId, getRootTaskId(), getDisplayId(),
+ "reParentTask:" + reason);
reparent(rootTask, position);
@@ -3331,14 +3326,6 @@ class Task extends TaskFragment {
}
});
}
- } else if (mBackGestureStarted) {
- // Cancel playing transitions if a back navigation animation is in progress.
- // This bit is set by {@link BackNavigationController} when a back gesture is started.
- // It is used as a one-off transition overwrite that is cleared when the back gesture
- // is committed and triggers a transition, or when the gesture is cancelled.
- mBackGestureStarted = false;
- mDisplayContent.mSkipAppTransitionAnimation = true;
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Skipping app transition animation. task=%s", this);
} else {
super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index b6c14bbfd8ed..6ff91af527ed 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -455,7 +455,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
}
mLastLeafTaskToFrontId = t.mTaskId;
- EventLogTags.writeWmTaskToFront(t.mUserId, t.mTaskId);
+ EventLogTags.writeWmTaskToFront(t.mUserId, t.mTaskId, getDisplayId());
// Notifying only when a leaf task moved to front. Or the listeners would be notified
// couple times from the leaf task all the way up to the root task.
mAtmService.getTaskChangeNotificationController().notifyTaskMovedToFront(t.getTaskInfo());
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index a64bd694605c..ef6859092689 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -928,10 +928,16 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
}
+ // Check whether the participants were animated from back navigation.
+ final boolean markBackAnimated = mController.mAtm.mBackNavigationController
+ .containsBackAnimationTargets(this);
// Resolve the animating targets from the participants
mTargets = calculateTargets(mParticipants, mChanges);
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges,
transaction);
+ if (markBackAnimated) {
+ mController.mAtm.mBackNavigationController.clearBackAnimations(mStartTransaction);
+ }
if (mOverrideOptions != null) {
info.setAnimationOptions(mOverrideOptions);
if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
@@ -1935,9 +1941,20 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
final Task task = wc.asTask();
if (task != null) {
final ActivityRecord topActivity = task.getTopNonFinishingActivity();
- if (topActivity != null && topActivity.mStartingData != null
- && topActivity.mStartingData.hasImeSurface()) {
- flags |= FLAG_WILL_IME_SHOWN;
+ if (topActivity != null) {
+ if (topActivity.mStartingData != null
+ && topActivity.mStartingData.hasImeSurface()) {
+ flags |= FLAG_WILL_IME_SHOWN;
+ }
+ if (topActivity.mAtmService.mBackNavigationController
+ .isMonitorTransitionTarget(topActivity)) {
+ flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+ }
+ } else {
+ if (task.mAtmService.mBackNavigationController
+ .isMonitorTransitionTarget(task)) {
+ flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+ }
}
if (task.voiceSession != null) {
flags |= FLAG_IS_VOICE_INTERACTION;
@@ -1951,6 +1968,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
flags |= FLAG_IS_VOICE_INTERACTION;
}
flags |= record.mTransitionChangeFlags;
+ if (record.mAtmService.mBackNavigationController
+ .isMonitorTransitionTarget(record)) {
+ flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+ }
}
final TaskFragment taskFragment = wc.asTaskFragment();
if (taskFragment != null && task == null) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 99a0e9032add..c56a22ef2bbe 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1842,7 +1842,7 @@ public class WindowManagerService extends IWindowManager.Stub
// Make this invalid which indicates a null attached frame.
outAttachedFrame.set(0, 0, -1, -1);
}
- outSizeCompatScale[0] = win.getSizeCompatScale();
+ outSizeCompatScale[0] = win.getSizeCompatScaleForClient();
}
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f06a8c9ffe82..17e64995fe8c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1267,8 +1267,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mGlobalScale = mInvGlobalScale = mSizeCompatScale = 1f;
}
- float getSizeCompatScale() {
- return mSizeCompatScale;
+ float getSizeCompatScaleForClient() {
+ // If the size compat scale is because of the size compat bounds, we only scale down its
+ // coordinates at the server side without letting the client know.
+ return mToken.hasSizeCompatBounds() ? 1f : mSizeCompatScale;
}
/**
@@ -3865,7 +3867,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
outFrames.attachedFrame.scale(mInvGlobalScale);
}
}
- outFrames.sizeCompatScale = mSizeCompatScale;
+
+ outFrames.sizeCompatScale = getSizeCompatScaleForClient();
// Note: in the cases where the window is tied to an activity, we should not send a
// configuration update when the window has requested to be hidden. Doing so can lead to
diff --git a/services/tests/mockingservicestests/src/com/android/server/DumpableDumperRule.java b/services/tests/mockingservicestests/src/com/android/server/DumpableDumperRule.java
new file mode 100644
index 000000000000..33275bd471f6
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/DumpableDumperRule.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 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;
+
+import android.util.Dumpable;
+import android.util.Log;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@code JUnit} rule that logs (using tag {@value #TAG} the contents of
+ * {@link Dumpable dumpables} in case of failure.
+ */
+public final class DumpableDumperRule implements TestRule {
+
+ private static final String TAG = DumpableDumperRule.class.getSimpleName();
+
+ private static final String[] NO_ARGS = {};
+
+ private final List<Dumpable> mDumpables = new ArrayList<>();
+
+ /**
+ * Adds a {@link Dumpable} to be logged if the test case fails.
+ */
+ public void addDumpable(Dumpable dumpable) {
+ mDumpables.add(dumpable);
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ base.evaluate();
+ } catch (Throwable t) {
+ dumpOnFailure(description.getMethodName());
+ throw t;
+ }
+ }
+ };
+ }
+
+ private void dumpOnFailure(String testName) throws IOException {
+ if (mDumpables.isEmpty()) {
+ return;
+ }
+ Log.w(TAG, "Dumping " + mDumpables.size() + " dumpables on failure of " + testName);
+ mDumpables.forEach(d -> logDumpable(d));
+ }
+
+ private void logDumpable(Dumpable dumpable) {
+ try {
+ try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
+ dumpable.dump(pw, NO_ARGS);
+ String[] dump = sw.toString().split(System.lineSeparator());
+ Log.w(TAG, "Dumping " + dumpable.getDumpableName() + " (" + dump.length
+ + " lines):");
+ for (String line : dump) {
+ Log.w(TAG, line);
+ }
+
+ } catch (RuntimeException e) {
+ Log.e(TAG, "RuntimeException dumping " + dumpable.getDumpableName(), e);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "IOException dumping " + dumpable.getDumpableName(), e);
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
index 9aa28ce741aa..c0b507068358 100644
--- a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
@@ -23,6 +23,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -38,6 +39,9 @@ public abstract class ExtendedMockitoTestCase {
private MockitoSession mSession;
+ @Rule
+ public final DumpableDumperRule mDumpableDumperRule = new DumpableDumperRule();
+
@Before
public void startSession() {
if (DEBUG) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
index 21f541f54790..923c3e385b5e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -44,14 +44,14 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
@Test
public void testAssignUserToDisplay_systemUser() {
- assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(USER_SYSTEM, SECONDARY_DISPLAY_ID));
+ assertThrows(IllegalArgumentException.class, () -> mMediator
+ .assignUserToDisplay(USER_SYSTEM, USER_SYSTEM, SECONDARY_DISPLAY_ID));
}
@Test
public void testAssignUserToDisplay_invalidDisplay() {
assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(USER_ID, INVALID_DISPLAY));
+ () -> mMediator.assignUserToDisplay(USER_ID, USER_ID, INVALID_DISPLAY));
}
@Test
@@ -59,7 +59,7 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
mockCurrentUser(USER_ID);
assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
+ () -> mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID));
assertNoUserAssignedToDisplay();
}
@@ -67,11 +67,10 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
@Test
public void testAssignUserToDisplay_startedProfileOfCurrentUser() {
mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
startDefaultProfile();
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator
+ .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID));
Log.v(TAG, "Exception: " + e);
assertNoUserAssignedToDisplay();
@@ -80,11 +79,10 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
@Test
public void testAssignUserToDisplay_stoppedProfileOfCurrentUser() {
mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
stopDefaultProfile();
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator
+ .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID));
Log.v(TAG, "Exception: " + e);
assertNoUserAssignedToDisplay();
@@ -92,17 +90,17 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
@Test
public void testAssignUserToDisplay_displayAvailable() {
- mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID);
assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
}
@Test
public void testAssignUserToDisplay_displayAlreadyAssigned() {
- mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID);
- IllegalStateException e = assertThrows(IllegalStateException.class,
- () -> mMediator.assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID));
+ IllegalStateException e = assertThrows(IllegalStateException.class, () -> mMediator
+ .assignUserToDisplay(OTHER_USER_ID, OTHER_USER_ID, SECONDARY_DISPLAY_ID));
Log.v(TAG, "Exception: " + e);
assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
@@ -112,10 +110,10 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
@Test
public void testAssignUserToDisplay_userAlreadyAssigned() {
- mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID);
IllegalStateException e = assertThrows(IllegalStateException.class,
- () -> mMediator.assignUserToDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID));
+ () -> mMediator.assignUserToDisplay(USER_ID, USER_ID, OTHER_SECONDARY_DISPLAY_ID));
Log.v(TAG, "Exception: " + e);
assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
@@ -127,11 +125,9 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
@Test
public void testAssignUserToDisplay_profileOnSameDisplayAsParent() {
- addDefaultProfileAndParent();
-
- mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+ mMediator.assignUserToDisplay(PARENT_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator
+ .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID));
Log.v(TAG, "Exception: " + e);
assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
@@ -139,11 +135,9 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
@Test
public void testAssignUserToDisplay_profileOnDifferentDisplayAsParent() {
- addDefaultProfileAndParent();
-
- mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID));
+ mMediator.assignUserToDisplay(PARENT_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator
+ .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID));
Log.v(TAG, "Exception: " + e);
assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
@@ -151,11 +145,9 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
@Test
public void testAssignUserToDisplay_profileDefaultDisplayParentOnSecondaryDisplay() {
- addDefaultProfileAndParent();
-
- mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY));
+ mMediator.assignUserToDisplay(PARENT_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator
+ .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, DEFAULT_DISPLAY));
Log.v(TAG, "Exception: " + e);
assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
@@ -201,7 +193,6 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
@Test
public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
- addDefaultProfileAndParent();
startDefaultProfile();
mockCurrentUser(PARENT_USER_ID);
assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
@@ -212,7 +203,6 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
@Test
public void testIsUserVisibleOnDisplay_stoppedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
- addDefaultProfileAndParent();
stopDefaultProfile();
mockCurrentUser(PARENT_USER_ID);
assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
@@ -223,7 +213,6 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
@Test
public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserOnUnassignedSecondaryDisplay() {
- addDefaultProfileAndParent();
startDefaultProfile();
mockCurrentUser(PARENT_USER_ID);
@@ -285,19 +274,6 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
.that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
}
- // TODO(b/244644281): scenario below shouldn't happen on "real life", as the profile cannot be
- // started on secondary display if its parent isn't, so we might need to remove (or refactor
- // this test) if/when the underlying logic changes
- @Test
- public void testGetUserAssignedToDisplay_profileOnSecondaryDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(USER_ID);
- assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
- .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
- }
-
// NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
// getUserAssignedToDisplay() for bg users relies only on the user / display assignments
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
index 7ae811793949..7af5f5d6b2fe 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -39,27 +39,25 @@ public final class UserVisibilityMediatorSUSDTest extends UserVisibilityMediator
mockCurrentUser(USER_ID);
assertThrows(UnsupportedOperationException.class,
- () -> mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
+ () -> mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID));
}
@Test
public void testAssignUserToDisplay_otherDisplay_startProfileOfcurrentUser() {
mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
startDefaultProfile();
- assertThrows(UnsupportedOperationException.class,
- () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+ assertThrows(UnsupportedOperationException.class, () -> mMediator
+ .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID));
}
@Test
public void testAssignUserToDisplay_otherDisplay_stoppedProfileOfcurrentUser() {
mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
stopDefaultProfile();
- assertThrows(UnsupportedOperationException.class,
- () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+ assertThrows(UnsupportedOperationException.class, () -> mMediator
+ .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID));
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 22e6e0dcae1c..7b20092b503a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -15,23 +15,27 @@
*/
package com.android.server.pm;
+import static android.content.pm.UserInfo.NO_PROFILE_GROUP_ID;
import static android.os.UserHandle.USER_NULL;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
+import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
+import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_FAILURE;
+import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserVisibilityMediator.startUserResultToString;
import static com.google.common.truth.Truth.assertWithMessage;
import android.annotation.UserIdInt;
-import android.util.SparseIntArray;
+import android.util.Log;
+
+import com.android.server.ExtendedMockitoTestCase;
import org.junit.Before;
import org.junit.Test;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
/**
* Base class for {@link UserVisibilityMediator} tests.
*
@@ -39,7 +43,33 @@ import java.util.Map;
* device mode (for example, whether the device supports concurrent multiple users on multiple
* displays or not).
*/
-abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrInternalTestCase {
+abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
+
+ private static final String TAG = UserVisibilityMediatorTestCase.class.getSimpleName();
+
+ /**
+ * Id for a simple user (that doesn't have profiles).
+ */
+ protected static final int USER_ID = 600;
+
+ /**
+ * Id for another simple user.
+ */
+ protected static final int OTHER_USER_ID = 666;
+
+ /**
+ * Id for a user that has one profile (whose id is {@link #PROFILE_USER_ID}.
+ *
+ * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
+ */
+ protected static final int PARENT_USER_ID = 642;
+
+ /**
+ * Id for a profile whose parent is {@link #PARENTUSER_ID}.
+ *
+ * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
+ */
+ protected static final int PROFILE_USER_ID = 643;
/**
* Id of a secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
@@ -51,12 +81,10 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern
*/
protected static final int OTHER_SECONDARY_DISPLAY_ID = 108;
- private final boolean mUsersOnSecondaryDisplaysEnabled;
+ private static final boolean FG = true;
+ private static final boolean BG = false;
- // TODO(b/244644281): manipulating mUsersOnSecondaryDisplays directly leaks implementation
- // details into the unit test, but it's fine for now as the tests were copied "as is" - it
- // would be better to use a geter() instead
- protected final SparseIntArray mUsersOnSecondaryDisplays = new SparseIntArray();
+ private final boolean mUsersOnSecondaryDisplaysEnabled;
protected UserVisibilityMediator mMediator;
@@ -66,13 +94,93 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern
@Before
public final void setMediator() {
- mMediator = new UserVisibilityMediator(mUms, mUsersOnSecondaryDisplaysEnabled,
- mUsersOnSecondaryDisplays);
+ mMediator = new UserVisibilityMediator(mUsersOnSecondaryDisplaysEnabled);
+ mDumpableDumperRule.addDumpable(mMediator);
+ }
+
+ @Test
+ public final void testStartUser_currentUser() {
+ int result = mMediator.startUser(USER_ID, USER_ID, FG, DEFAULT_DISPLAY);
+ assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
+
+ assertCurrentUser(USER_ID);
+ assertIsCurrentUserOrRunningProfileOfCurrentUser(USER_ID);
+ assertStartedProfileGroupIdOf(USER_ID, USER_ID);
+ }
+
+ @Test
+ public final void testStartUser_currentUserSecondaryDisplay() {
+ int result = mMediator.startUser(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, START_USER_RESULT_FAILURE);
+
+ assertCurrentUser(INITIAL_CURRENT_USER_ID);
+ assertIsNotCurrentUserOrRunningProfileOfCurrentUser(USER_ID);
+ assertStartedProfileGroupIdOf(USER_ID, NO_PROFILE_GROUP_ID);
+ }
+
+ @Test
+ public final void testStartUser_profileBg_parentStarted() {
+ mockCurrentUser(PARENT_USER_ID);
+
+ int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
+ assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
+
+ assertCurrentUser(PARENT_USER_ID);
+ assertIsCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
+ assertStartedProfileGroupIdOf(PROFILE_USER_ID, PARENT_USER_ID);
+ assertIsStartedProfile(PROFILE_USER_ID);
+ }
+
+ @Test
+ public final void testStartUser_profileBg_parentNotStarted() {
+ int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
+ assertStartUserResult(result, START_USER_RESULT_SUCCESS_INVISIBLE);
+
+ assertCurrentUser(INITIAL_CURRENT_USER_ID);
+ assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
+ assertStartedProfileGroupIdOf(PROFILE_USER_ID, PARENT_USER_ID);
+ assertIsStartedProfile(PROFILE_USER_ID);
+ }
+
+ @Test
+ public final void testStartUser_profileBg_secondaryDisplay() {
+ int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, START_USER_RESULT_FAILURE);
+
+ assertCurrentUser(INITIAL_CURRENT_USER_ID);
+ assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
+ }
+
+ @Test
+ public final void testStartUser_profileFg() {
+ int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, FG, DEFAULT_DISPLAY);
+ assertStartUserResult(result, START_USER_RESULT_FAILURE);
+
+ assertCurrentUser(INITIAL_CURRENT_USER_ID);
+ assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
+ assertStartedProfileGroupIdOf(PROFILE_USER_ID, NO_PROFILE_GROUP_ID);
+ }
+
+ @Test
+ public final void testStartUser_profileFgSecondaryDisplay() {
+ int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, FG, SECONDARY_DISPLAY_ID);
+
+ assertStartUserResult(result, START_USER_RESULT_FAILURE);
+ assertCurrentUser(INITIAL_CURRENT_USER_ID);
+ }
+
+ @Test
+ public final void testGetStartedProfileGroupId_whenStartedWithNoProfileGroupId() {
+ int result = mMediator.startUser(USER_ID, NO_PROFILE_GROUP_ID, FG, DEFAULT_DISPLAY);
+ assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
+
+ assertWithMessage("shit").that(mMediator.getStartedProfileGroupId(USER_ID))
+ .isEqualTo(USER_ID);
}
@Test
public final void testAssignUserToDisplay_defaultDisplayIgnored() {
- mMediator.assignUserToDisplay(USER_ID, DEFAULT_DISPLAY);
+ mMediator.assignUserToDisplay(USER_ID, USER_ID, DEFAULT_DISPLAY);
assertNoUserAssignedToDisplay();
}
@@ -103,18 +211,14 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern
@Test
public final void testIsUserVisible_startedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
mockCurrentUser(PARENT_USER_ID);
startDefaultProfile();
- setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
-
assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID)
.that(mMediator.isUserVisible(PROFILE_USER_ID)).isTrue();
}
@Test
public final void testIsUserVisible_stoppedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
mockCurrentUser(PARENT_USER_ID);
stopDefaultProfile();
@@ -164,7 +268,6 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern
@Test
public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserInvalidDisplay() {
- addDefaultProfileAndParent();
mockCurrentUser(PARENT_USER_ID);
startDefaultProfile();
@@ -174,7 +277,6 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern
@Test
public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserInvalidDisplay() {
- addDefaultProfileAndParent();
mockCurrentUser(PARENT_USER_ID);
stopDefaultProfile();
@@ -184,18 +286,14 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern
@Test
public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserDefaultDisplay() {
- addDefaultProfileAndParent();
mockCurrentUser(PARENT_USER_ID);
startDefaultProfile();
- setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
-
assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
.that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
}
@Test
public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserDefaultDisplay() {
- addDefaultProfileAndParent();
mockCurrentUser(PARENT_USER_ID);
stopDefaultProfile();
@@ -205,18 +303,14 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern
@Test
public final void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplay() {
- addDefaultProfileAndParent();
mockCurrentUser(PARENT_USER_ID);
startDefaultProfile();
- setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
-
assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
.that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
}
@Test
public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserSecondaryDisplay() {
- addDefaultProfileAndParent();
mockCurrentUser(PARENT_USER_ID);
stopDefaultProfile();
@@ -250,11 +344,8 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern
@Test
public final void testGetDisplayAssignedToUser_startedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
mockCurrentUser(PARENT_USER_ID);
startDefaultProfile();
- setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
-
assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
.that(mMediator.getDisplayAssignedToUser(PROFILE_USER_ID))
.isEqualTo(DEFAULT_DISPLAY);
@@ -262,7 +353,6 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern
@Test
public final void testGetDisplayAssignedToUser_stoppedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
mockCurrentUser(PARENT_USER_ID);
stopDefaultProfile();
@@ -296,28 +386,96 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern
.isEqualTo(USER_ID);
}
- // NOTE: should only called by tests that indirectly needs to check user assignments (like
- // isUserVisible), not by tests for the user assignment methods per se.
+ // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
+ // it's not meant to be used to test startUser() itself.
+ protected void mockCurrentUser(@UserIdInt int userId) {
+ Log.d(TAG, "mockCurrentUser(" + userId + ")");
+ int result = mMediator.startUser(userId, userId, FG, DEFAULT_DISPLAY);
+ if (result != START_USER_RESULT_SUCCESS_VISIBLE) {
+ throw new IllegalStateException("Failed to mock current user " + userId
+ + ": mediator returned " + startUserResultToString(result));
+ }
+ }
+
+ // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
+ // it's not meant to be used to test startUser() itself.
+ protected void startDefaultProfile() {
+ mockCurrentUser(PARENT_USER_ID);
+ Log.d(TAG, "starting default profile (" + PROFILE_USER_ID + ") in background after starting"
+ + " its parent (" + PARENT_USER_ID + ") on foreground");
+
+ int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
+ if (result != START_USER_RESULT_SUCCESS_VISIBLE) {
+ throw new IllegalStateException("Failed to start profile user " + PROFILE_USER_ID
+ + ": mediator returned " + startUserResultToString(result));
+ }
+ }
+
+ // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
+ // it's not meant to be used to test stopUser() itself.
+ protected void stopDefaultProfile() {
+ Log.d(TAG, "stopping default profile");
+ mMediator.stopUser(PROFILE_USER_ID);
+ }
+
+ // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
+ // it's not meant to be used to test assignUserToDisplay() itself.
protected final void assignUserToDisplay(@UserIdInt int userId, int displayId) {
- mUsersOnSecondaryDisplays.put(userId, displayId);
+ Log.d(TAG, "assignUserToDisplay(" + userId + ", " + displayId + ")");
+ int result = mMediator.startUser(userId, userId, BG, displayId);
+ if (result != START_USER_RESULT_SUCCESS_INVISIBLE) {
+ throw new IllegalStateException("Failed to startuser " + userId
+ + " on background: mediator returned " + startUserResultToString(result));
+ }
+ mMediator.assignUserToDisplay(userId, userId, displayId);
+
}
protected final void assertNoUserAssignedToDisplay() {
- assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
+ assertWithMessage("uses on secondary displays")
+ .that(mMediator.getUsersOnSecondaryDisplays())
.isEmpty();
}
protected final void assertUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
- assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
+ assertWithMessage("uses on secondary displays")
+ .that(mMediator.getUsersOnSecondaryDisplays())
.containsExactly(userId, displayId);
}
- private Map<Integer, Integer> usersOnSecondaryDisplaysAsMap() {
- int size = mUsersOnSecondaryDisplays.size();
- Map<Integer, Integer> map = new LinkedHashMap<>(size);
- for (int i = 0; i < size; i++) {
- map.put(mUsersOnSecondaryDisplays.keyAt(i), mUsersOnSecondaryDisplays.valueAt(i));
- }
- return map;
+ private void assertCurrentUser(@UserIdInt int userId) {
+ assertWithMessage("mediator.getCurrentUserId()").that(mMediator.getCurrentUserId())
+ .isEqualTo(userId);
+ }
+
+ private void assertIsStartedProfile(@UserIdInt int userId) {
+ assertWithMessage("mediator.isStartedProfile(%s)", userId)
+ .that(mMediator.isStartedProfile(userId))
+ .isTrue();
+ }
+
+ private void assertStartedProfileGroupIdOf(@UserIdInt int profileId, @UserIdInt int parentId) {
+ assertWithMessage("mediator.getStartedProfileGroupId(%s)", profileId)
+ .that(mMediator.getStartedProfileGroupId(profileId))
+ .isEqualTo(parentId);
+ }
+
+ private void assertIsCurrentUserOrRunningProfileOfCurrentUser(int userId) {
+ assertWithMessage("mediator.isCurrentUserOrRunningProfileOfCurrentUser(%s)", userId)
+ .that(mMediator.isCurrentUserOrRunningProfileOfCurrentUser(userId))
+ .isTrue();
+ }
+
+ private void assertIsNotCurrentUserOrRunningProfileOfCurrentUser(int userId) {
+ assertWithMessage("mediator.isCurrentUserOrRunningProfileOfCurrentUser(%s)", userId)
+ .that(mMediator.isCurrentUserOrRunningProfileOfCurrentUser(userId))
+ .isFalse();
+ }
+
+ private void assertStartUserResult(int actualResult, int expectedResult) {
+ assertWithMessage("startUser() result (where %s=%s and %s=%s)",
+ actualResult, startUserResultToString(actualResult),
+ expectedResult, startUserResultToString(expectedResult))
+ .that(actualResult).isEqualTo(expectedResult);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 935d1d880a2a..80cee50cef79 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -38,6 +38,7 @@ import static com.android.server.am.UserController.USER_COMPLETED_EVENT_MSG;
import static com.android.server.am.UserController.USER_CURRENT_MSG;
import static com.android.server.am.UserController.USER_START_MSG;
import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG;
+import static com.android.server.am.UserController.USER_VISIBILITY_CHANGED_MSG;
import static com.google.android.collect.Lists.newArrayList;
import static com.google.android.collect.Sets.newHashSet;
@@ -158,6 +159,7 @@ public class UserControllerTest {
REPORT_USER_SWITCH_MSG,
USER_SWITCH_TIMEOUT_MSG,
USER_START_MSG,
+ USER_VISIBILITY_CHANGED_MSG,
USER_CURRENT_MSG);
private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
@@ -283,7 +285,7 @@ public class UserControllerTest {
assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes())
.containsExactly(USER_START_MSG);
- verifyUserAssignedToDisplay(TEST_PRE_CREATED_USER_ID, Display.DEFAULT_DISPLAY);
+ verifyUserNeverAssignedToDisplay();
}
private void startUserAssertions(
@@ -948,11 +950,13 @@ public class UserControllerTest {
}
private void verifyUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
- verify(mInjector.getUserManagerInternal()).assignUserToDisplay(userId, displayId);
+ verify(mInjector.getUserManagerInternal()).assignUserToDisplay(eq(userId), anyInt(),
+ anyBoolean(), eq(displayId));
}
private void verifyUserNeverAssignedToDisplay() {
- verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplay(anyInt(), anyInt());
+ verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplay(anyInt(), anyInt(),
+ anyBoolean(), anyInt());
}
private void verifyUserUnassignedFromDisplay(@UserIdInt int userId) {
@@ -964,7 +968,7 @@ public class UserControllerTest {
}
private void verifySystemUserVisibilityChangedNotified(boolean visible) {
- verify(mInjector).notifySystemUserVisibilityChanged(visible);
+ verify(mInjector).onUserVisibilityChanged(UserHandle.USER_SYSTEM, visible);
}
// Should be public to allow mocking
@@ -1104,13 +1108,13 @@ public class UserControllerTest {
}
@Override
- void onUserStarting(@UserIdInt int userId, boolean visible) {
- Log.i(TAG, "onUserStarting(" + userId + ", " + visible + ")");
+ void onUserStarting(@UserIdInt int userId) {
+ Log.i(TAG, "onUserStarting(" + userId + ")");
}
@Override
- void notifySystemUserVisibilityChanged(boolean visible) {
- Log.i(TAG, "notifySystemUserVisibilityChanged(" + visible + ")");
+ void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
+ Log.i(TAG, "onUserVisibilityChanged(" + userId + ", " + visible + ")");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index f1383119605e..808130aa9568 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -155,7 +155,18 @@ public class JobStoreTest {
}
@Test
- public void testWritingTwoFilesToDisk() throws Exception {
+ public void testWritingTwoJobsToDisk_singleFile() throws Exception {
+ mTaskStoreUnderTest.setUseSplitFiles(false);
+ runWritingTwoJobsToDisk();
+ }
+
+ @Test
+ public void testWritingTwoJobsToDisk_splitFiles() throws Exception {
+ mTaskStoreUnderTest.setUseSplitFiles(true);
+ runWritingTwoJobsToDisk();
+ }
+
+ private void runWritingTwoJobsToDisk() throws Exception {
final JobInfo task1 = new Builder(8, mComponent)
.setRequiresDeviceIdle(true)
.setPeriodic(10000L)
@@ -169,8 +180,10 @@ public class JobStoreTest {
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.setPersisted(true)
.build();
- final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, SOME_UID, null, -1, null);
- final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, SOME_UID, null, -1, null);
+ final int uid1 = SOME_UID;
+ final int uid2 = uid1 + 1;
+ final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
+ final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
mTaskStoreUnderTest.add(taskStatus1);
mTaskStoreUnderTest.add(taskStatus2);
waitForPendingIo();
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 c906abcdc986..e5842b400ba3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -25,6 +25,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
@@ -71,6 +72,7 @@ import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.times;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -87,6 +89,8 @@ import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.view.InsetsFrameProvider;
+import android.view.InsetsSource;
+import android.view.InsetsVisibilities;
import android.view.WindowManager;
import androidx.test.filters.MediumTest;
@@ -105,6 +109,9 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.List;
/**
* Tests for Size Compatibility mode.
@@ -2368,6 +2375,48 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ public void testLetterboxDetailsForTaskBar_letterboxNotOverlappingTaskBar() {
+ mAtm.mDevEnableNonResizableMultiWindow = true;
+ final int screenHeight = 2200;
+ final int screenWidth = 1400;
+ final int taskbarHeight = 200;
+ setUpDisplaySizeWithApp(screenWidth, screenHeight);
+
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+ // Move first activity to split screen which takes half of the screen.
+ organizer.mPrimary.setBounds(0, screenHeight / 2, screenWidth, screenHeight);
+ organizer.putTaskToPrimary(mTask, true);
+
+ final InsetsSource navSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+ navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
+
+ mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15);
+
+ final WindowState w1 = addWindowToActivity(mActivity);
+ w1.mAboveInsetsState.addSource(navSource);
+
+ // Prepare unresizable activity with max aspect ratio
+ prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+ // Refresh the letterboxes
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ final ArgumentCaptor<Rect> cropCapturer = ArgumentCaptor.forClass(Rect.class);
+ verify(mTransaction, times(2)).setWindowCrop(
+ eq(w1.getSurfaceControl()),
+ cropCapturer.capture()
+ );
+ final List<Rect> capturedCrops = cropCapturer.getAllValues();
+
+ final int expectedHeight = screenHeight / 2 - taskbarHeight;
+ assertEquals(2, capturedCrops.size());
+ assertEquals(expectedHeight, capturedCrops.get(0).bottom);
+ assertEquals(expectedHeight, capturedCrops.get(1).bottom);
+ }
+
+ @Test
public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() {
mAtm.mDevEnableNonResizableMultiWindow = true;
setUpDisplaySizeWithApp(2800, 1000);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 7f5beb1f6cf0..4fd2b78b6f66 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2392,6 +2392,8 @@ public class UsageStatsService extends SystemService implements
@Override
public void setAppStandbyBucket(String packageName, int bucket, int userId) {
+ super.setAppStandbyBucket_enforcePermission();
+
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final long token = Binder.clearCallingIdentity();
@@ -2442,6 +2444,8 @@ public class UsageStatsService extends SystemService implements
@Override
public void setAppStandbyBuckets(ParceledListSlice appBuckets, int userId) {
+ super.setAppStandbyBuckets_enforcePermission();
+
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final long token = Binder.clearCallingIdentity();
@@ -2493,6 +2497,8 @@ public class UsageStatsService extends SystemService implements
public void setEstimatedLaunchTime(String packageName, long estimatedLaunchTime,
int userId) {
+ super.setEstimatedLaunchTime_enforcePermission();
+
final long token = Binder.clearCallingIdentity();
try {
UsageStatsService.this
@@ -2506,6 +2512,8 @@ public class UsageStatsService extends SystemService implements
@Override
public void setEstimatedLaunchTimes(ParceledListSlice estimatedLaunchTimes, int userId) {
+ super.setEstimatedLaunchTimes_enforcePermission();
+
final long token = Binder.clearCallingIdentity();
try {
UsageStatsService.this
diff --git a/telecomm/OWNERS b/telecomm/OWNERS
index eb0c4327ec46..dcaf858a0a0b 100644
--- a/telecomm/OWNERS
+++ b/telecomm/OWNERS
@@ -4,3 +4,7 @@ breadley@google.com
tgunn@google.com
xiaotonj@google.com
rgreenwalt@google.com
+chinmayd@google.com
+grantmenke@google.com
+pmadapurmath@google.com
+tjstuart@google.com \ No newline at end of file
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index b22e2b76a477..936fad51f095 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8785,7 +8785,7 @@ public class CarrierConfigManager {
* The default value is 30 minutes.
*
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR
- * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED
+ * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED
*/
public static final String
KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index bfa60ba960ca..6be2f770efe9 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -138,13 +138,6 @@ public class ServiceState implements Parcelable {
*/
public static final int FREQUENCY_RANGE_MMWAVE = 4;
- private static final List<Integer> FREQUENCY_RANGE_ORDER = Arrays.asList(
- FREQUENCY_RANGE_UNKNOWN,
- FREQUENCY_RANGE_LOW,
- FREQUENCY_RANGE_MID,
- FREQUENCY_RANGE_HIGH,
- FREQUENCY_RANGE_MMWAVE);
-
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "DUPLEX_MODE_",
@@ -2108,15 +2101,6 @@ public class ServiceState implements Parcelable {
}
/**
- * @hide
- */
- public static final int getBetterNRFrequencyRange(int range1, int range2) {
- return FREQUENCY_RANGE_ORDER.indexOf(range1) > FREQUENCY_RANGE_ORDER.indexOf(range2)
- ? range1
- : range2;
- }
-
- /**
* Returns a copy of self with location-identifying information removed.
* Always clears the NetworkRegistrationInfo's CellIdentity fields, but if removeCoarseLocation
* is true, clears other info as well.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 51a7840a9fbe..e099e69e3edd 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17324,14 +17324,14 @@ public class TelephonyManager {
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12;
/**
- * Purchase premium capability failed because the network is congested.
+ * Purchase premium capability failed because the entitlement check failed.
* Subsequent attempts will be throttled for the amount of time specified by
* {@link CarrierConfigManager
* #KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
* and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
* Throttling will be reevaluated when the network is no longer congested.
*/
- public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13;
+ public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED = 13;
/**
* Purchase premium capability failed because the request was not made on the default data
@@ -17368,7 +17368,7 @@ public class TelephonyManager {
PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT,
PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
- PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED,
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED,
PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB,
PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
public @interface PurchasePremiumCapabilityResult {}
@@ -17407,8 +17407,8 @@ public class TelephonyManager {
return "REQUEST_FAILED";
case PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE:
return "NETWORK_NOT_AVAILABLE";
- case PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED:
- return "NETWORK_CONGESTED";
+ case PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED:
+ return "ENTITLEMENT_CHECK_FAILED";
case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB:
return "NOT_DEFAULT_DATA_SUB";
case PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP:
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 73aff4351785..a834e2bbd0d1 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -468,14 +468,14 @@ public final class DataCallResponse implements Parcelable {
final boolean isQosBearerSessionsSame =
(mQosBearerSessions == null || other.mQosBearerSessions == null)
? mQosBearerSessions == other.mQosBearerSessions
- : mQosBearerSessions.size() == other.mQosBearerSessions.size()
- && mQosBearerSessions.containsAll(other.mQosBearerSessions);
+ : (mQosBearerSessions.size() == other.mQosBearerSessions.size()
+ && mQosBearerSessions.containsAll(other.mQosBearerSessions));
final boolean isTrafficDescriptorsSame =
(mTrafficDescriptors == null || other.mTrafficDescriptors == null)
? mTrafficDescriptors == other.mTrafficDescriptors
- : mTrafficDescriptors.size() == other.mTrafficDescriptors.size()
- && mTrafficDescriptors.containsAll(other.mTrafficDescriptors);
+ : (mTrafficDescriptors.size() == other.mTrafficDescriptors.size()
+ && mTrafficDescriptors.containsAll(other.mTrafficDescriptors));
return mCause == other.mCause
&& mSuggestedRetryTime == other.mSuggestedRetryTime
@@ -504,10 +504,35 @@ public final class DataCallResponse implements Parcelable {
@Override
public int hashCode() {
+ // Generate order-independent hashes for lists
+ int addressesHash = mAddresses.stream()
+ .map(LinkAddress::hashCode)
+ .mapToInt(Integer::intValue)
+ .sum();
+ int dnsAddressesHash = mDnsAddresses.stream()
+ .map(InetAddress::hashCode)
+ .mapToInt(Integer::intValue)
+ .sum();
+ int gatewayAddressesHash = mGatewayAddresses.stream()
+ .map(InetAddress::hashCode)
+ .mapToInt(Integer::intValue)
+ .sum();
+ int pcscfAddressesHash = mPcscfAddresses.stream()
+ .map(InetAddress::hashCode)
+ .mapToInt(Integer::intValue)
+ .sum();
+ int qosBearerSessionsHash = mQosBearerSessions.stream()
+ .map(QosBearerSession::hashCode)
+ .mapToInt(Integer::intValue)
+ .sum();
+ int trafficDescriptorsHash = mTrafficDescriptors.stream()
+ .map(TrafficDescriptor::hashCode)
+ .mapToInt(Integer::intValue)
+ .sum();
return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType,
- mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses,
- mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, mDefaultQos,
- mQosBearerSessions, mSliceInfo, mTrafficDescriptors);
+ mInterfaceName, addressesHash, dnsAddressesHash, gatewayAddressesHash,
+ pcscfAddressesHash, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId,
+ mDefaultQos, qosBearerSessionsHash, mSliceInfo, trafficDescriptorsHash);
}
@Override
@@ -816,8 +841,8 @@ public final class DataCallResponse implements Parcelable {
/**
* Set pdu session id.
* <p/>
- * The id must be between 1 and 15 when linked to a pdu session. If no pdu session
- * exists for the current data call, the id must be set to {@link PDU_SESSION_ID_NOT_SET}.
+ * The id must be between 1 and 15 when linked to a pdu session. If no pdu session
+ * exists for the current data call, the id must be set to {@link #PDU_SESSION_ID_NOT_SET}.
*
* @param pduSessionId Pdu Session Id of the data call.
* @return The same instance of the builder.
@@ -858,6 +883,7 @@ public final class DataCallResponse implements Parcelable {
*/
public @NonNull Builder setQosBearerSessions(
@NonNull List<QosBearerSession> qosBearerSessions) {
+ Objects.requireNonNull(qosBearerSessions);
mQosBearerSessions = qosBearerSessions;
return this;
}
@@ -891,6 +917,7 @@ public final class DataCallResponse implements Parcelable {
*/
public @NonNull Builder setTrafficDescriptors(
@NonNull List<TrafficDescriptor> trafficDescriptors) {
+ Objects.requireNonNull(trafficDescriptors);
mTrafficDescriptors = trafficDescriptors;
return this;
}
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index d9d5c14735ea..e78a1e107afe 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -660,9 +660,6 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu
if (!first.getEmergencyUrns().equals(second.getEmergencyUrns())) {
return false;
}
- if (first.getEmergencyCallRouting() != second.getEmergencyCallRouting()) {
- return false;
- }
// Never merge two numbers if one of them is from test mode but the other one is not;
// This supports to remove a number from the test mode.
if (first.isFromSources(EMERGENCY_NUMBER_SOURCE_TEST)
@@ -685,12 +682,18 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu
public static EmergencyNumber mergeSameEmergencyNumbers(@NonNull EmergencyNumber first,
@NonNull EmergencyNumber second) {
if (areSameEmergencyNumbers(first, second)) {
+ int routing = first.getEmergencyCallRouting();
+
+ if (second.isFromSources(EMERGENCY_NUMBER_SOURCE_DATABASE)) {
+ routing = second.getEmergencyCallRouting();
+ }
+
return new EmergencyNumber(first.getNumber(), first.getCountryIso(), first.getMnc(),
first.getEmergencyServiceCategoryBitmask(),
first.getEmergencyUrns(),
first.getEmergencyNumberSourceBitmask()
| second.getEmergencyNumberSourceBitmask(),
- first.getEmergencyCallRouting());
+ routing);
}
return null;
}
diff --git a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
index dd9b294a9596..afaeca1f76ae 100644
--- a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
+++ b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
@@ -15,7 +15,6 @@
*/
package com.android.frameworks.perftests.job;
-
import android.app.job.JobInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -46,7 +45,8 @@ import java.util.List;
public class JobStorePerfTests {
private static final String SOURCE_PACKAGE = "com.android.frameworks.perftests.job";
private static final int SOURCE_USER_ID = 0;
- private static final int CALLING_UID = 10079;
+ private static final int BASE_CALLING_UID = 10079;
+ private static final int MAX_UID_COUNT = 10;
private static Context sContext;
private static File sTestDir;
@@ -65,10 +65,10 @@ public class JobStorePerfTests {
sJobStore = JobStore.initAndGetForTesting(sContext, sTestDir);
for (int i = 0; i < 50; i++) {
- sFewJobs.add(createJobStatus("fewJobs", i));
+ sFewJobs.add(createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % MAX_UID_COUNT)));
}
for (int i = 0; i < 500; i++) {
- sManyJobs.add(createJobStatus("manyJobs", i));
+ sManyJobs.add(createJobStatus("manyJobs", i, BASE_CALLING_UID + (i % MAX_UID_COUNT)));
}
}
@@ -104,6 +104,64 @@ public class JobStorePerfTests {
runPersistedJobWriting(sManyJobs);
}
+ private void runPersistedJobWriting_delta(List<JobStatus> jobList,
+ List<JobStatus> jobAdditions, List<JobStatus> jobRemovals) {
+ final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
+
+ long elapsedTimeNs = 0;
+ while (benchmarkState.keepRunning(elapsedTimeNs)) {
+ sJobStore.clearForTesting();
+ for (JobStatus job : jobList) {
+ sJobStore.addForTesting(job);
+ }
+ sJobStore.writeStatusToDiskForTesting();
+
+ for (JobStatus job : jobAdditions) {
+ sJobStore.addForTesting(job);
+ }
+ for (JobStatus job : jobRemovals) {
+ sJobStore.removeForTesting(job);
+ }
+
+ final long startTime = SystemClock.elapsedRealtimeNanos();
+ sJobStore.writeStatusToDiskForTesting();
+ final long endTime = SystemClock.elapsedRealtimeNanos();
+ elapsedTimeNs = endTime - startTime;
+ }
+ }
+
+ @Test
+ public void testPersistedJobWriting_delta_fewJobs() {
+ List<JobStatus> additions = new ArrayList<>();
+ List<JobStatus> removals = new ArrayList<>();
+ final int numModifiedUids = MAX_UID_COUNT / 2;
+ for (int i = 0; i < sFewJobs.size() / 3; ++i) {
+ JobStatus job = createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % numModifiedUids));
+ if (i % 2 == 0) {
+ additions.add(job);
+ } else {
+ removals.add(job);
+ }
+ }
+ runPersistedJobWriting_delta(sFewJobs, additions, removals);
+ }
+
+ @Test
+ public void testPersistedJobWriting_delta_manyJobs() {
+ List<JobStatus> additions = new ArrayList<>();
+ List<JobStatus> removals = new ArrayList<>();
+ final int numModifiedUids = MAX_UID_COUNT / 2;
+ for (int i = 0; i < sManyJobs.size() / 3; ++i) {
+ JobStatus job = createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % numModifiedUids));
+ if (i % 2 == 0) {
+ additions.add(job);
+ } else {
+ removals.add(job);
+ }
+ }
+ runPersistedJobWriting_delta(sManyJobs, additions, removals);
+ }
+
private void runPersistedJobReading(List<JobStatus> jobList, boolean rtcIsGood) {
final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
@@ -144,12 +202,12 @@ public class JobStorePerfTests {
runPersistedJobReading(sManyJobs, false);
}
- private static JobStatus createJobStatus(String testTag, int jobId) {
+ private static JobStatus createJobStatus(String testTag, int jobId, int callingUid) {
JobInfo jobInfo = new JobInfo.Builder(jobId,
new ComponentName(sContext, "JobStorePerfTestJobService"))
.setPersisted(true)
.build();
return JobStatus.createFromJobInfo(
- jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+ jobInfo, callingUid, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
}
}
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
index 3da8b460df13..133c1767c9b4 100644
--- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
@@ -147,12 +147,39 @@ public class BroadcastInterceptingContext extends ContextWrapper {
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
- return registerReceiver(receiver, filter, null, null);
+ return registerReceiver(receiver, filter, null, null, 0);
+ }
+
+ /**
+ * Registers the specified {@code receiver} to listen for broadcasts that match the {@code
+ * filter} in the current process.
+ *
+ * <p>Since this method only listens for broadcasts in the current process, the provided {@code
+ * flags} are ignored; this method is primarily intended to allow receivers that register with
+ * flags to register in the current process during tests.
+ */
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
+ return registerReceiver(receiver, filter, null, null, flags);
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
+ return registerReceiver(receiver, filter, broadcastPermission, scheduler, 0);
+ }
+
+ /**
+ * Registers the specified {@code receiver} to listen for broadcasts that match the {@code
+ * filter} to run in the context of the specified {@code scheduler} in the current process.
+ *
+ * <p>Since this method only listens for broadcasts in the current process, the provided {@code
+ * flags} are ignored; this method is primarily intended to allow receivers that register with
+ * flags to register in the current process during tests.
+ */
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler, int flags) {
synchronized (mInterceptors) {
mInterceptors.add(new BroadcastInterceptor(receiver, filter, scheduler));
}