summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/Android.bp1
-rw-r--r--api/api.go39
-rw-r--r--core/api/current.txt42
-rw-r--r--core/api/system-current.txt3
-rw-r--r--core/java/android/app/AppOpsManager.java87
-rw-r--r--core/java/android/app/BroadcastOptions.java34
-rw-r--r--core/java/android/app/ComponentOptions.java34
-rw-r--r--core/java/android/app/IBackupAgent.aidl13
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java111
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl3
-rw-r--r--core/java/android/app/backup/BackupAgent.java29
-rw-r--r--core/java/android/app/backup/BackupRestoreEventLogger.aidl19
-rw-r--r--core/java/android/app/backup/BackupRestoreEventLogger.java58
-rw-r--r--core/java/android/app/prediction/AppPredictor.java43
-rw-r--r--core/java/android/app/time/DetectorStatusTypes.java227
-rw-r--r--core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl19
-rw-r--r--core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java363
-rw-r--r--core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl19
-rw-r--r--core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java96
-rw-r--r--core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java66
-rw-r--r--core/java/android/app/time/TimeZoneDetectorStatus.aidl19
-rw-r--r--core/java/android/app/time/TimeZoneDetectorStatus.java124
-rw-r--r--core/java/android/app/timezonedetector/TimeZoneDetector.java4
-rw-r--r--core/java/android/provider/DeviceConfig.java36
-rw-r--r--core/java/android/provider/Settings.java109
-rw-r--r--core/java/android/view/inputmethod/TextAppearanceInfo.java517
-rw-r--r--core/java/android/widget/Editor.java41
-rw-r--r--core/java/android/window/IBackAnimationFinishedCallback.aidl2
-rw-r--r--core/java/android/window/WindowProvider.java9
-rw-r--r--core/java/android/window/WindowProviderService.java48
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java2
-rw-r--r--core/proto/android/app/location_time_zone_manager.proto2
-rw-r--r--core/proto/android/app/time_zone_detector.proto29
-rw-r--r--core/res/AndroidManifest.xml6
-rw-r--r--core/tests/coretests/src/android/app/activity/BroadcastTest.java12
-rw-r--r--core/tests/coretests/src/android/app/backup/BackupAgentTest.java42
-rw-r--r--core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java65
-rw-r--r--core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java103
-rw-r--r--core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java210
-rw-r--r--core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java68
-rw-r--r--core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java105
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java41
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-en-rCA/strings.xml26
-rw-r--r--packages/SettingsLib/SpaPrivileged/res/values-en-rXC/strings.xml26
-rw-r--r--packages/SettingsLib/res/values-en-rCA/arrays.xml66
-rw-r--r--packages/SettingsLib/res/values-en-rCA/strings.xml154
-rw-r--r--packages/SettingsLib/res/values-hi/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-nb/arrays.xml6
-rw-r--r--packages/SettingsLib/res/values-nb/strings.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Utils.java48
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java6
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java8
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt10
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt7
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt7
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt18
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt8
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt15
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt7
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt7
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt26
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt28
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt26
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt26
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt28
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt30
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt25
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt31
-rw-r--r--packages/SystemUI/proguard.flags6
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rCA/strings.xml8
-rw-r--r--packages/SystemUI/res/layout/keyguard_status_bar.xml25
-rw-r--r--packages/SystemUI/res/layout/status_bar.xml25
-rw-r--r--packages/SystemUI/res/layout/status_bar_user_chip_container.xml40
-rw-r--r--packages/SystemUI/res/values-en-rCA/strings_tv.xml6
-rw-r--r--packages/SystemUI/res/values/dimens.xml5
-rw-r--r--packages/SystemUI/res/values/strings.xml3
-rw-r--r--packages/SystemUI/res/values/styles.xml6
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java26
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt134
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt112
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt199
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt102
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt306
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java3
-rw-r--r--services/Android.bp17
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java7
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java2
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java16
-rw-r--r--services/core/java/com/android/server/pm/AppsFilterImpl.java25
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java17
-rw-r--r--services/core/java/com/android/server/pm/InstallingSession.java10
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageMetrics.java4
-rw-r--r--services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java94
-rw-r--r--services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java194
-rw-r--r--services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java12
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java8
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java9
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java7
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java52
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java7
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java248
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java3
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/DisabledLocationTimeZoneProvider.java86
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java28
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java18
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java66
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java52
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java215
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java6
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java74
-rw-r--r--services/core/java/com/android/server/wm/Task.java7
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java96
-rw-r--r--services/permission/Android.bp40
-rw-r--r--services/permission/OWNERS4
-rw-r--r--services/permission/jarjar-rules.txt1
-rw-r--r--services/permission/java/com/android/server/permission/ModernPermissionManagerServiceImpl.kt26
-rw-r--r--services/proguard_permission.flags9
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java49
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java46
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java175
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java21
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java33
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java37
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java531
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java292
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java3
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java2
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java42
-rw-r--r--telephony/java/com/android/internal/telephony/RILConstants.java3
173 files changed, 6186 insertions, 1970 deletions
diff --git a/api/Android.bp b/api/Android.bp
index a3e64a565422..37b5d4c5f47e 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -113,6 +113,7 @@ combined_apis {
"framework-sdksandbox",
"framework-tethering",
"framework-uwb",
+ "framework-virtualization",
"framework-wifi",
"i18n.module.public.api",
],
diff --git a/api/api.go b/api/api.go
index 6a6c493e041a..ba0fdc18d23e 100644
--- a/api/api.go
+++ b/api/api.go
@@ -27,8 +27,16 @@ import (
const art = "art.module.public.api"
const conscrypt = "conscrypt.module.public.api"
const i18n = "i18n.module.public.api"
+const virtualization = "framework-virtualization"
var core_libraries_modules = []string{art, conscrypt, i18n}
+// List of modules that are not yet updatable, and hence they can still compile
+// against hidden APIs. These modules are filtered out when building the
+// updatable-framework-module-impl (because updatable-framework-module-impl is
+// built against module_current SDK). Instead they are directly statically
+// linked into the all-framework-module-lib, which is building against hidden
+// APIs.
+var non_updatable_modules = []string{virtualization}
// The intention behind this soong plugin is to generate a number of "merged"
// API-related modules that would otherwise require a large amount of very
@@ -249,12 +257,31 @@ func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) {
// This module is for the "framework-all" module, which should not include the core libraries.
modules = removeAll(modules, core_libraries_modules)
- props := libraryProps{}
- props.Name = proptools.StringPtr("all-framework-module-impl")
- props.Static_libs = transformArray(modules, "", ".impl")
- props.Sdk_version = proptools.StringPtr("module_current")
- props.Visibility = []string{"//frameworks/base"}
- ctx.CreateModule(java.LibraryFactory, &props)
+ // Remove the modules that belong to non-updatable APEXes since those are allowed to compile
+ // against unstable APIs.
+ modules = removeAll(modules, non_updatable_modules)
+ // First create updatable-framework-module-impl, which contains all updatable modules.
+ // This module compiles against module_lib SDK.
+ {
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("updatable-framework-module-impl")
+ props.Static_libs = transformArray(modules, "", ".impl")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+ }
+
+ // Now create all-framework-module-impl, which contains updatable-framework-module-impl
+ // and all non-updatable modules. This module compiles against hidden APIs.
+ {
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("all-framework-module-impl")
+ props.Static_libs = transformArray(non_updatable_modules, "", ".impl")
+ props.Static_libs = append(props.Static_libs, "updatable-framework-module-impl")
+ props.Sdk_version = proptools.StringPtr("core_platform")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+ }
}
func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) {
diff --git a/core/api/current.txt b/core/api/current.txt
index 1c9b6c3a5fe5..f80c09596fb1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -44040,6 +44040,8 @@ package android.telephony {
field public static final int APPTYPE_USIM = 2; // 0x2
field public static final int AUTHTYPE_EAP_AKA = 129; // 0x81
field public static final int AUTHTYPE_EAP_SIM = 128; // 0x80
+ field public static final int AUTHTYPE_GBA_BOOTSTRAP = 132; // 0x84
+ field public static final int AUTHTYPE_GBA_NAF_KEY_EXTERNAL = 133; // 0x85
field public static final int CALL_COMPOSER_STATUS_OFF = 0; // 0x0
field public static final int CALL_COMPOSER_STATUS_ON = 1; // 0x1
field public static final int CALL_STATE_IDLE = 0; // 0x0
@@ -54041,23 +54043,22 @@ package android.view.inputmethod {
}
public final class TextAppearanceInfo implements android.os.Parcelable {
- ctor public TextAppearanceInfo(@NonNull android.widget.TextView);
method public int describeContents();
- method @Nullable public String getFontFamilyName();
method @Nullable public String getFontFeatureSettings();
method @Nullable public String getFontVariationSettings();
+ method @ColorInt public int getHighlightTextColor();
+ method @ColorInt public int getHintTextColor();
method public float getLetterSpacing();
method public int getLineBreakStyle();
method public int getLineBreakWordStyle();
- method public int getMaxLength();
+ method @ColorInt public int getLinkTextColor();
+ method @ColorInt public int getShadowColor();
method @Px public float getShadowDx();
method @Px public float getShadowDy();
method @Px public float getShadowRadius();
+ method @Nullable public String getSystemFontFamilyName();
method @ColorInt public int getTextColor();
- method @ColorInt public int getTextColorHighlight();
- method @ColorInt public int getTextColorHint();
- method @Nullable public android.content.res.ColorStateList getTextColorLink();
- method @IntRange(from=0xffffffff, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) public int getTextFontWeight();
+ method @IntRange(from=android.graphics.fonts.FontStyle.FONT_WEIGHT_UNSPECIFIED, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) public int getTextFontWeight();
method @NonNull public android.os.LocaleList getTextLocales();
method public float getTextScaleX();
method @Px public float getTextSize();
@@ -54069,6 +54070,33 @@ package android.view.inputmethod {
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextAppearanceInfo> CREATOR;
}
+ public static final class TextAppearanceInfo.Builder {
+ ctor public TextAppearanceInfo.Builder();
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo build();
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setAllCaps(boolean);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setElegantTextHeight(boolean);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFallbackLineSpacing(boolean);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFontFeatureSettings(@Nullable String);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFontVariationSettings(@Nullable String);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setHighlightTextColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setHintTextColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLetterSpacing(float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLineBreakStyle(int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLineBreakWordStyle(int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLinkTextColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowDx(@Px float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowDy(@Px float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowRadius(@Px float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setSystemFontFamilyName(@Nullable String);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextFontWeight(@IntRange(from=android.graphics.fonts.FontStyle.FONT_WEIGHT_UNSPECIFIED, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextLocales(@NonNull android.os.LocaleList);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextScaleX(float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextSize(@Px float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextStyle(int);
+ }
+
public final class TextAttribute implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.os.PersistableBundle getExtras();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 51694133024c..cb9b76dd5c22 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1109,6 +1109,7 @@ package android.app.admin {
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public java.util.Set<java.lang.Integer> getApplicationExemptions(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
@@ -1134,6 +1135,7 @@ package android.app.admin {
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, android.Manifest.permission.PROVISION_DEMO_DEVICE}) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
+ method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
@@ -1155,6 +1157,7 @@ package android.app.admin {
field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER";
+ field public static final int EXEMPT_FROM_APP_STANDBY = 0; // 0x0
field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER";
field public static final String EXTRA_LOST_MODE_LOCATION = "android.app.extra.LOST_MODE_LOCATION";
field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1b3282e752f4..d5879fb523ce 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1407,9 +1407,41 @@ public class AppOpsManager {
public static final int OP_FOREGROUND_SERVICE_SPECIAL_USE =
AppProtoEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE;
+ /**
+ * Exempt from start foreground service from background restriction.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final int OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION =
+ AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION;
+
+ /**
+ * Exempt from start foreground service from background with while in user permission
+ * restriction.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final int OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION =
+ AppProtoEnums
+ .APP_OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION;
+
+ /**
+ * Hide foreground service stop button in quick settings.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final int OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON =
+ AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 128;
+ public static final int _NUM_OP = 131;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1947,6 +1979,38 @@ public class AppOpsManager {
public static final String OPSTR_FOREGROUND_SERVICE_SPECIAL_USE =
"android:foreground_service_special_use";
+ /**
+ * Exempt from start foreground service from background restriction.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final String OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION =
+ "android:system_exempt_from_fgs_bg_start_restriction";
+
+ /**
+ * Exempt from start foreground service from background with while in user permission
+ * restriction.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final String
+ OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION =
+ "android:system_exempt_from_fgs_bg_start_while_in_use_permission_restriction";
+
+ /**
+ * Hide foreground service stop button in quick settings.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final String OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON =
+ "android:system_exempt_from_fgs_stop_button";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2441,6 +2505,17 @@ public class AppOpsManager {
new AppOpInfo.Builder(OP_FOREGROUND_SERVICE_SPECIAL_USE,
OPSTR_FOREGROUND_SERVICE_SPECIAL_USE, "FOREGROUND_SERVICE_SPECIAL_USE")
.setPermission(Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE).build(),
+ new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION,
+ OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION,
+ "SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION").build(),
+ new AppOpInfo.Builder(
+ OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION,
+ OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION,
+ "SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION")
+ .build(),
+ new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON,
+ OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON,
+ "SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON").build()
};
// The number of longs needed to form a full bitmask of app ops
@@ -2498,12 +2573,6 @@ public class AppOpsManager {
sPermToOp.put(sAppOpInfos[op].permission, op);
}
}
-
- if ((_NUM_OP + Long.SIZE - 1) / Long.SIZE != 2) {
- // The code currently assumes that the length of sAppOpsNotedInThisBinderTransaction is
- // two longs
- throw new IllegalStateException("notedAppOps collection code assumes < 128 appops");
- }
}
/** Config used to control app ops access messages sampling */
@@ -2603,8 +2672,8 @@ public class AppOpsManager {
if (boxedOpCode != null) {
return boxedOpCode;
}
- if (HealthConnectManager.isHealthPermission(ActivityThread.currentApplication(),
- permission)) {
+ if (permission != null && HealthConnectManager.isHealthPermission(
+ ActivityThread.currentApplication(), permission)) {
return OP_READ_WRITE_HEALTH_DATA;
}
return OP_NONE;
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 45d44589b2d8..1777f37202c2 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -63,7 +63,6 @@ public class BroadcastOptions extends ComponentOptions {
private long mRequireCompatChangeId = CHANGE_INVALID;
private boolean mRequireCompatChangeEnabled = true;
private boolean mIsAlarmBroadcast = false;
- private boolean mIsInteractiveBroadcast = false;
private long mIdForResponseEvent;
private @Nullable IntentFilter mRemoveMatchingFilter;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
@@ -171,13 +170,6 @@ public class BroadcastOptions extends ComponentOptions {
"android:broadcast.is_alarm";
/**
- * Corresponds to {@link #setInteractiveBroadcast(boolean)}
- * @hide
- */
- public static final String KEY_INTERACTIVE_BROADCAST =
- "android:broadcast.is_interactive";
-
- /**
* @hide
* @deprecated Use {@link android.os.PowerExemptionManager#
* TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead.
@@ -308,7 +300,6 @@ public class BroadcastOptions extends ComponentOptions {
mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
- mIsInteractiveBroadcast = opts.getBoolean(KEY_INTERACTIVE_BROADCAST, false);
mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
IntentFilter.class);
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
@@ -629,28 +620,6 @@ public class BroadcastOptions extends ComponentOptions {
}
/**
- * When set, this broadcast will be understood as having originated from
- * some direct interaction by the user such as a notification tap or button
- * press. Only the OS itself may use this option.
- * @hide
- * @param broadcastIsInteractive
- * @see #isInteractiveBroadcast()
- */
- @RequiresPermission(android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE)
- public void setInteractiveBroadcast(boolean broadcastIsInteractive) {
- mIsInteractiveBroadcast = broadcastIsInteractive;
- }
-
- /**
- * Did this broadcast originate with a direct user interaction?
- * @return true if this broadcast is the result of an interaction, false otherwise
- * @hide
- */
- public boolean isInteractiveBroadcast() {
- return mIsInteractiveBroadcast;
- }
-
- /**
* Did this broadcast originate from a push message from the server?
*
* @return true if this broadcast is a push message, false otherwise.
@@ -837,9 +806,6 @@ public class BroadcastOptions extends ComponentOptions {
if (mIsAlarmBroadcast) {
b.putBoolean(KEY_ALARM_BROADCAST, true);
}
- if (mIsInteractiveBroadcast) {
- b.putBoolean(KEY_INTERACTIVE_BROADCAST, true);
- }
if (mMinManifestReceiverApiLevel != 0) {
b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel);
}
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index 4e5e384a2798..74db39f63830 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -16,6 +16,7 @@
package android.app;
+import android.annotation.RequiresPermission;
import android.os.Bundle;
/**
@@ -45,8 +46,15 @@ public class ComponentOptions {
public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION =
"android.pendingIntent.backgroundActivityAllowedByPermission";
+ /**
+ * Corresponds to {@link #setInteractive(boolean)}
+ * @hide
+ */
+ public static final String KEY_INTERACTIVE = "android:component.isInteractive";
+
private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT;
private boolean mPendingIntentBalAllowedByPermission = false;
+ private boolean mIsInteractive = false;
ComponentOptions() {
}
@@ -61,6 +69,29 @@ public class ComponentOptions {
setPendingIntentBackgroundActivityLaunchAllowedByPermission(
opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
false));
+ mIsInteractive = opts.getBoolean(KEY_INTERACTIVE, false);
+ }
+
+ /**
+ * When set, a broadcast will be understood as having originated from
+ * some direct interaction by the user such as a notification tap or button
+ * press. Only the OS itself may use this option.
+ * @hide
+ * @param interactive
+ * @see #isInteractive()
+ */
+ @RequiresPermission(android.Manifest.permission.COMPONENT_OPTION_INTERACTIVE)
+ public void setInteractive(boolean interactive) {
+ mIsInteractive = interactive;
+ }
+
+ /**
+ * Did this PendingIntent send originate with a direct user interaction?
+ * @return true if this is the result of an interaction, false otherwise
+ * @hide
+ */
+ public boolean isInteractive() {
+ return mIsInteractive;
}
/**
@@ -103,6 +134,9 @@ public class ComponentOptions {
b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
mPendingIntentBalAllowedByPermission);
}
+ if (mIsInteractive) {
+ b.putBoolean(KEY_INTERACTIVE, true);
+ }
return b;
}
}
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index 37c5cabc2376..811118479ef8 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -16,9 +16,12 @@
package android.app;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.IBackupCallback;
import android.app.backup.IBackupManager;
import android.os.ParcelFileDescriptor;
+
+import com.android.internal.infra.AndroidFuture;
/**
* Interface presented by applications being asked to participate in the
@@ -193,4 +196,14 @@ oneway interface IBackupAgent {
* @param message The message to be passed to the agent's application in an exception.
*/
void fail(String message);
+
+ /**
+ * Provides the logging results that were accumulated in the BackupAgent during a backup or
+ * restore operation. This method should be called after the agent completes its backup or
+ * restore.
+ *
+ * @param resultsFuture a future that is completed with the logging results.
+ */
+ void getLoggerResults(
+ in AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> resultsFuture);
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6fedb41884ec..be4df9d24c25 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -103,6 +103,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.net.NetworkUtilsInternal;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.org.conscrypt.TrustedCertificateStore;
@@ -3823,6 +3824,27 @@ public class DevicePolicyManager {
public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1;
/**
+ * Prevent an app from being placed into app standby buckets, such that it will not be subject
+ * to device resources restrictions as a result of app standby buckets.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int EXEMPT_FROM_APP_STANDBY = 0;
+
+ /**
+ * Exemptions to platform restrictions, given to an application through
+ * {@link #setApplicationExemptions(String, Set)}.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "EXEMPT_FROM_"}, value = {
+ EXEMPT_FROM_APP_STANDBY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ApplicationExemptionConstants {}
+
+ /**
* Broadcast action: notify system apps (e.g. settings, SysUI, etc) that the device management
* resources with IDs {@link #EXTRA_RESOURCE_IDS} has been updated, the updated resources can be
* retrieved using {@link DevicePolicyResourcesManager#getDrawable} and
@@ -14727,6 +14749,95 @@ public class DevicePolicyManager {
}
/**
+ * Service-specific error code used in {@link #setApplicationExemptions(String, Set)} and
+ * {@link #getApplicationExemptions(String)}.
+ * @hide
+ */
+ public static final int ERROR_PACKAGE_NAME_NOT_FOUND = 1;
+
+ /**
+ * Called by an application with the
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS} permission, to
+ * grant platform restriction exemptions to a given application.
+ *
+ * @param packageName The package name of the application to be exempt.
+ * @param exemptions The set of exemptions to be applied.
+ * @throws SecurityException If the caller does not have
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS}
+ * @throws NameNotFoundException If either the package is not installed or the package is not
+ * visible to the caller.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS)
+ public void setApplicationExemptions(@NonNull String packageName,
+ @NonNull @ApplicationExemptionConstants Set<Integer> exemptions)
+ throws NameNotFoundException {
+ throwIfParentInstance("setApplicationExemptions");
+ if (mService != null) {
+ try {
+ mService.setApplicationExemptions(packageName,
+ ArrayUtils.convertToIntArray(new ArraySet<>(exemptions)));
+ } catch (ServiceSpecificException e) {
+ switch (e.errorCode) {
+ case ERROR_PACKAGE_NAME_NOT_FOUND:
+ throw new NameNotFoundException(e.getMessage());
+ default:
+ throw new RuntimeException(
+ "Unknown error setting application exemptions: " + e.errorCode, e);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns all the platform restriction exemptions currently applied to an application. Called
+ * by an application with the
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS} permission.
+ *
+ * @param packageName The package name to check.
+ * @return A set of platform restrictions an application is exempt from.
+ * @throws SecurityException If the caller does not have
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS}
+ * @throws NameNotFoundException If either the package is not installed or the package is not
+ * visible to the caller.
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS)
+ public Set<Integer> getApplicationExemptions(@NonNull String packageName)
+ throws NameNotFoundException {
+ throwIfParentInstance("getApplicationExemptions");
+ if (mService == null) {
+ return Collections.emptySet();
+ }
+ try {
+ return intArrayToSet(mService.getApplicationExemptions(packageName));
+ } catch (ServiceSpecificException e) {
+ switch (e.errorCode) {
+ case ERROR_PACKAGE_NAME_NOT_FOUND:
+ throw new NameNotFoundException(e.getMessage());
+ default:
+ throw new RuntimeException(
+ "Unknown error getting application exemptions: " + e.errorCode, e);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private Set<Integer> intArrayToSet(int[] array) {
+ Set<Integer> set = new ArraySet<>();
+ for (int item : array) {
+ set.add(item);
+ }
+ return set;
+ }
+
+ /**
* Called by a device owner or a profile owner to disable user control over apps. User will not
* be able to clear app data or force-stop packages. When called by a device owner, applies to
* all users on the device. Starting from Android 13, packages with user control disabled are
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 6c27dd7b771b..8a4026539267 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -568,4 +568,7 @@ interface IDevicePolicyManager {
boolean shouldAllowBypassingDevicePolicyManagementRoleQualification();
List<UserHandle> getPolicyManagedProfiles(in UserHandle userHandle);
+
+ void setApplicationExemptions(String packageName, in int[]exemptions);
+ int[] getApplicationExemptions(String packageName);
}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index b1b59b0e39b1..a4f612d7faee 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -41,6 +41,7 @@ import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import libcore.io.IoUtils;
@@ -202,6 +203,7 @@ public abstract class BackupAgent extends ContextWrapper {
Handler mHandler = null;
+ @Nullable private volatile BackupRestoreEventLogger mLogger = null;
@Nullable private UserHandle mUser;
// This field is written from the main thread (in onCreate), and read in a Binder thread (in
// onFullBackup that is called from system_server via Binder).
@@ -234,6 +236,20 @@ public abstract class BackupAgent extends ContextWrapper {
} catch (InterruptedException e) { /* ignored */ }
}
+ /**
+ * Get a logger to record app-specific backup and restore events that are happening during a
+ * backup or restore operation.
+ *
+ * <p>The logger instance had been created by the system with the correct {@link
+ * BackupRestoreEventLogger.OperationType} that corresponds to the operation the {@code
+ * BackupAgent} is currently handling.
+ *
+ * @hide
+ */
+ @Nullable
+ public BackupRestoreEventLogger getBackupRestoreEventLogger() {
+ return mLogger;
+ }
public BackupAgent() {
super(null);
@@ -264,6 +280,9 @@ public abstract class BackupAgent extends ContextWrapper {
* @hide
*/
public void onCreate(UserHandle user, @OperationType int operationType) {
+ // TODO: Instantiate with the correct type using a parameter.
+ mLogger = new BackupRestoreEventLogger(BackupRestoreEventLogger.OperationType.BACKUP);
+
onCreate();
mUser = user;
@@ -1305,6 +1324,16 @@ public abstract class BackupAgent extends ContextWrapper {
}
}
}
+
+ @Override
+ public void getLoggerResults(
+ AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> in) {
+ if (mLogger != null) {
+ in.complete(mLogger.getLoggingResults());
+ } else {
+ in.complete(Collections.emptyList());
+ }
+ }
}
static class FailRunnable implements Runnable {
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.aidl b/core/java/android/app/backup/BackupRestoreEventLogger.aidl
new file mode 100644
index 000000000000..d6ef4e64258d
--- /dev/null
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.app.backup;
+
+parcelable BackupRestoreEventLogger.DataTypeResult; \ No newline at end of file
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index 6f62c8a03078..68740cb3c086 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -19,6 +19,10 @@ package android.app.backup;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
import android.util.Slog;
import java.lang.annotation.Retention;
@@ -312,7 +316,7 @@ public class BackupRestoreEventLogger {
/**
* Encapsulate logging results for a single data type.
*/
- public static class DataTypeResult {
+ public static class DataTypeResult implements Parcelable {
@BackupRestoreDataType
private final String mDataType;
private int mSuccessCount;
@@ -362,5 +366,57 @@ public class BackupRestoreEventLogger {
public byte[] getMetadataHash() {
return mMetadataHash;
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mDataType);
+
+ dest.writeInt(mSuccessCount);
+
+ dest.writeInt(mFailCount);
+
+ Bundle errorsBundle = new Bundle();
+ for (Map.Entry<String, Integer> e : mErrors.entrySet()) {
+ errorsBundle.putInt(e.getKey(), e.getValue());
+ }
+ dest.writeBundle(errorsBundle);
+
+ dest.writeByteArray(mMetadataHash);
+ }
+
+ public static final Parcelable.Creator<DataTypeResult> CREATOR =
+ new Parcelable.Creator<>() {
+ public DataTypeResult createFromParcel(Parcel in) {
+ String dataType = in.readString();
+
+ int successCount = in.readInt();
+
+ int failCount = in.readInt();
+
+ Map<String, Integer> errors = new ArrayMap<>();
+ Bundle errorsBundle = in.readBundle(getClass().getClassLoader());
+ for (String key : errorsBundle.keySet()) {
+ errors.put(key, errorsBundle.getInt(key));
+ }
+
+ byte[] metadataHash = in.createByteArray();
+
+ DataTypeResult result = new DataTypeResult(dataType);
+ result.mSuccessCount = successCount;
+ result.mFailCount = failCount;
+ result.mErrors.putAll(errors);
+ result.mMetadataHash = metadataHash;
+ return result;
+ }
+
+ public DataTypeResult[] newArray(int size) {
+ return new DataTypeResult[size];
+ }
+ };
}
}
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index 2581daa2f68b..d628b7f92c6c 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -30,6 +30,8 @@ import android.os.ServiceManager;
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import dalvik.system.CloseGuard;
import java.util.List;
@@ -79,6 +81,7 @@ public final class AppPredictor {
private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
private final AppPredictionSessionId mSessionId;
+ @GuardedBy("itself")
private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
/**
@@ -94,7 +97,7 @@ public final class AppPredictor {
IBinder b = ServiceManager.getService(Context.APP_PREDICTION_SERVICE);
mPredictionManager = IPredictionManager.Stub.asInterface(b);
mSessionId = new AppPredictionSessionId(
- context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
+ context.getPackageName() + ":" + UUID.randomUUID(), context.getUserId());
try {
mPredictionManager.createPredictionSession(predictionContext, mSessionId, getToken());
} catch (RemoteException e) {
@@ -155,6 +158,15 @@ public final class AppPredictor {
*/
public void registerPredictionUpdates(@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull AppPredictor.Callback callback) {
+ synchronized (mRegisteredCallbacks) {
+ registerPredictionUpdatesLocked(callbackExecutor, callback);
+ }
+ }
+
+ @GuardedBy("mRegisteredCallbacks")
+ private void registerPredictionUpdatesLocked(
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull AppPredictor.Callback callback) {
if (mIsClosed.get()) {
throw new IllegalStateException("This client has already been destroyed.");
}
@@ -183,6 +195,13 @@ public final class AppPredictor {
* @param callback The callback to be unregistered.
*/
public void unregisterPredictionUpdates(@NonNull AppPredictor.Callback callback) {
+ synchronized (mRegisteredCallbacks) {
+ unregisterPredictionUpdatesLocked(callback);
+ }
+ }
+
+ @GuardedBy("mRegisteredCallbacks")
+ private void unregisterPredictionUpdatesLocked(@NonNull AppPredictor.Callback callback) {
if (mIsClosed.get()) {
throw new IllegalStateException("This client has already been destroyed.");
}
@@ -235,7 +254,7 @@ public final class AppPredictor {
}
try {
- mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice(targets),
+ mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice<>(targets),
new CallbackWrapper(callbackExecutor, callback));
} catch (RemoteException e) {
Log.e(TAG, "Failed to sort targets", e);
@@ -251,19 +270,25 @@ public final class AppPredictor {
if (!mIsClosed.getAndSet(true)) {
mCloseGuard.close();
- // Do destroy;
- try {
- mPredictionManager.onDestroyPredictionSession(mSessionId);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to notify app target event", e);
- e.rethrowAsRuntimeException();
+ synchronized (mRegisteredCallbacks) {
+ destroySessionLocked();
}
- mRegisteredCallbacks.clear();
} else {
throw new IllegalStateException("This client has already been destroyed.");
}
}
+ @GuardedBy("mRegisteredCallbacks")
+ private void destroySessionLocked() {
+ try {
+ mPredictionManager.onDestroyPredictionSession(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify app target event", e);
+ e.rethrowAsRuntimeException();
+ }
+ mRegisteredCallbacks.clear();
+ }
+
@Override
protected void finalize() throws Throwable {
try {
diff --git a/core/java/android/app/time/DetectorStatusTypes.java b/core/java/android/app/time/DetectorStatusTypes.java
new file mode 100644
index 000000000000..3643fc9a7d86
--- /dev/null
+++ b/core/java/android/app/time/DetectorStatusTypes.java
@@ -0,0 +1,227 @@
+/*
+ * 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 android.app.time;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A set of constants that can relate to time or time zone detector status.
+ *
+ * <ul>
+ * <li>Detector status - the status of the overall detector.</li>
+ * <li>Detection algorithm status - the status of an algorithm that a detector can use.
+ * Each detector is expected to have one or more known algorithms to detect its chosen property,
+ * e.g. for time zone devices can have a "location" detection algorithm, where the device's
+ * location is used to detect the time zone.</li>
+ * </ul>
+ *
+ * @hide
+ */
+public final class DetectorStatusTypes {
+
+ /** A status code for a detector. */
+ @IntDef(prefix = "DETECTOR_STATUS_", value = {
+ DETECTOR_STATUS_UNKNOWN,
+ DETECTOR_STATUS_NOT_SUPPORTED,
+ DETECTOR_STATUS_NOT_RUNNING,
+ DETECTOR_STATUS_RUNNING,
+ })
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DetectorStatus {}
+
+ /**
+ * The detector status is unknown. Expected only for use as a placeholder before the actual
+ * status is known.
+ */
+ public static final @DetectorStatus int DETECTOR_STATUS_UNKNOWN = 0;
+
+ /** The detector is not supported on this device. */
+ public static final @DetectorStatus int DETECTOR_STATUS_NOT_SUPPORTED = 1;
+
+ /** The detector is supported but is not running. */
+ public static final @DetectorStatus int DETECTOR_STATUS_NOT_RUNNING = 2;
+
+ /** The detector is supported and is running. */
+ public static final @DetectorStatus int DETECTOR_STATUS_RUNNING = 3;
+
+ private DetectorStatusTypes() {}
+
+ /**
+ * A status code for a detection algorithm.
+ */
+ @IntDef(prefix = "DETECTION_ALGORITHM_STATUS_", value = {
+ DETECTION_ALGORITHM_STATUS_UNKNOWN,
+ DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED,
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ })
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DetectionAlgorithmStatus {}
+
+ /**
+ * The detection algorithm status is unknown. Expected only for use as a placeholder before the
+ * actual status is known.
+ */
+ public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_UNKNOWN = 0;
+
+ /** The detection algorithm is not supported on this device. */
+ public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED = 1;
+
+ /** The detection algorithm supported but is not running. */
+ public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_NOT_RUNNING = 2;
+
+ /** The detection algorithm supported and is running. */
+ public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_RUNNING = 3;
+
+ /**
+ * Validates the supplied value is one of the known {@code DETECTOR_STATUS_} constants and
+ * returns it if it is valid. {@link #DETECTOR_STATUS_UNKNOWN} is considered valid.
+ *
+ * @throws IllegalArgumentException if the value is not recognized
+ */
+ public static @DetectorStatus int requireValidDetectorStatus(
+ @DetectorStatus int detectorStatus) {
+ if (detectorStatus < DETECTOR_STATUS_UNKNOWN || detectorStatus > DETECTOR_STATUS_RUNNING) {
+ throw new IllegalArgumentException("Invalid detector status: " + detectorStatus);
+ }
+ return detectorStatus;
+ }
+
+ /**
+ * Returns a string for each {@code DETECTOR_STATUS_} constant. See also
+ * {@link #detectorStatusFromString(String)}.
+ *
+ * @throws IllegalArgumentException if the value is not recognized
+ */
+ @NonNull
+ public static String detectorStatusToString(@DetectorStatus int detectorStatus) {
+ switch (detectorStatus) {
+ case DETECTOR_STATUS_UNKNOWN:
+ return "UNKNOWN";
+ case DETECTOR_STATUS_NOT_SUPPORTED:
+ return "NOT_SUPPORTED";
+ case DETECTOR_STATUS_NOT_RUNNING:
+ return "NOT_RUNNING";
+ case DETECTOR_STATUS_RUNNING:
+ return "RUNNING";
+ default:
+ throw new IllegalArgumentException("Unknown status: " + detectorStatus);
+ }
+ }
+
+ /**
+ * Returns {@code DETECTOR_STATUS_} constant value from a string. See also
+ * {@link #detectorStatusToString(int)}.
+ *
+ * @throws IllegalArgumentException if the value is not recognized or is invalid
+ */
+ public static @DetectorStatus int detectorStatusFromString(
+ @Nullable String detectorStatusString) {
+ if (TextUtils.isEmpty(detectorStatusString)) {
+ throw new IllegalArgumentException("Empty status: " + detectorStatusString);
+ }
+
+ switch (detectorStatusString) {
+ case "UNKNOWN":
+ return DETECTOR_STATUS_UNKNOWN;
+ case "NOT_SUPPORTED":
+ return DETECTOR_STATUS_NOT_SUPPORTED;
+ case "NOT_RUNNING":
+ return DETECTOR_STATUS_NOT_RUNNING;
+ case "RUNNING":
+ return DETECTOR_STATUS_RUNNING;
+ default:
+ throw new IllegalArgumentException("Unknown status: " + detectorStatusString);
+ }
+ }
+
+ /**
+ * Validates the supplied value is one of the known {@code DETECTION_ALGORITHM_} constants and
+ * returns it if it is valid. {@link #DETECTION_ALGORITHM_STATUS_UNKNOWN} is considered valid.
+ *
+ * @throws IllegalArgumentException if the value is not recognized
+ */
+ public static @DetectionAlgorithmStatus int requireValidDetectionAlgorithmStatus(
+ @DetectionAlgorithmStatus int detectionAlgorithmStatus) {
+ if (detectionAlgorithmStatus < DETECTION_ALGORITHM_STATUS_UNKNOWN
+ || detectionAlgorithmStatus > DETECTION_ALGORITHM_STATUS_RUNNING) {
+ throw new IllegalArgumentException(
+ "Invalid detection algorithm: " + detectionAlgorithmStatus);
+ }
+ return detectionAlgorithmStatus;
+ }
+
+ /**
+ * Returns a string for each {@code DETECTION_ALGORITHM_} constant. See also
+ * {@link #detectionAlgorithmStatusFromString(String)}
+ *
+ * @throws IllegalArgumentException if the value is not recognized
+ */
+ @NonNull
+ public static String detectionAlgorithmStatusToString(
+ @DetectionAlgorithmStatus int detectorAlgorithmStatus) {
+ switch (detectorAlgorithmStatus) {
+ case DETECTION_ALGORITHM_STATUS_UNKNOWN:
+ return "UNKNOWN";
+ case DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED:
+ return "NOT_SUPPORTED";
+ case DETECTION_ALGORITHM_STATUS_NOT_RUNNING:
+ return "NOT_RUNNING";
+ case DETECTION_ALGORITHM_STATUS_RUNNING:
+ return "RUNNING";
+ default:
+ throw new IllegalArgumentException("Unknown status: " + detectorAlgorithmStatus);
+ }
+ }
+
+ /**
+ * Returns {@code DETECTION_ALGORITHM_} constant value from a string. See also
+ * {@link #detectionAlgorithmStatusToString(int)} (String)}
+ *
+ * @throws IllegalArgumentException if the value is not recognized or is invalid
+ */
+ public static @DetectionAlgorithmStatus int detectionAlgorithmStatusFromString(
+ @Nullable String detectorAlgorithmStatusString) {
+
+ if (TextUtils.isEmpty(detectorAlgorithmStatusString)) {
+ throw new IllegalArgumentException("Empty status: " + detectorAlgorithmStatusString);
+ }
+
+ switch (detectorAlgorithmStatusString) {
+ case "UNKNOWN":
+ return DETECTION_ALGORITHM_STATUS_UNKNOWN;
+ case "NOT_SUPPORTED":
+ return DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+ case "NOT_RUNNING":
+ return DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+ case "RUNNING":
+ return DETECTION_ALGORITHM_STATUS_RUNNING;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown status: " + detectorAlgorithmStatusString);
+ }
+ }
+}
diff --git a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl
new file mode 100644
index 000000000000..7184b123af1c
--- /dev/null
+++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.app.time;
+
+parcelable LocationTimeZoneAlgorithmStatus;
diff --git a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
new file mode 100644
index 000000000000..710b8c40cefe
--- /dev/null
+++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
@@ -0,0 +1,363 @@
+/*
+ * 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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
+import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusFromString;
+import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusToString;
+import static android.app.time.DetectorStatusTypes.requireValidDetectionAlgorithmStatus;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.timezone.TimeZoneProviderStatus;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Information about the status of the location-based time zone detection algorithm.
+ *
+ * @hide
+ */
+public final class LocationTimeZoneAlgorithmStatus implements Parcelable {
+
+ /**
+ * An enum that describes a location time zone provider's status.
+ *
+ * @hide
+ */
+ @IntDef(prefix = "PROVIDER_STATUS_", value = {
+ PROVIDER_STATUS_NOT_PRESENT,
+ PROVIDER_STATUS_NOT_READY,
+ PROVIDER_STATUS_IS_CERTAIN,
+ PROVIDER_STATUS_IS_UNCERTAIN,
+ })
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProviderStatus {}
+
+ /**
+ * Indicates a provider is not present because it has not been configured, the configuration
+ * is bad, or the provider has reported a permanent failure.
+ */
+ public static final @ProviderStatus int PROVIDER_STATUS_NOT_PRESENT = 1;
+
+ /**
+ * Indicates a provider has not reported it is certain or uncertain. This may be because it has
+ * just started running, or it has been stopped.
+ */
+ public static final @ProviderStatus int PROVIDER_STATUS_NOT_READY = 2;
+
+ /**
+ * Indicates a provider last reported it is certain.
+ */
+ public static final @ProviderStatus int PROVIDER_STATUS_IS_CERTAIN = 3;
+
+ /**
+ * Indicates a provider last reported it is uncertain.
+ */
+ public static final @ProviderStatus int PROVIDER_STATUS_IS_UNCERTAIN = 4;
+
+ /**
+ * An instance that provides no information about algorithm status because the algorithm has not
+ * yet reported. Effectively a "null" status placeholder.
+ */
+ @NonNull
+ public static final LocationTimeZoneAlgorithmStatus UNKNOWN =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_UNKNOWN,
+ PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+
+ private final @DetectionAlgorithmStatus int mStatus;
+ private final @ProviderStatus int mPrimaryProviderStatus;
+ // May be populated when mPrimaryProviderReportedStatus == PROVIDER_STATUS_IS_CERTAIN
+ // or PROVIDER_STATUS_IS_UNCERTAIN
+ @Nullable private final TimeZoneProviderStatus mPrimaryProviderReportedStatus;
+
+ private final @ProviderStatus int mSecondaryProviderStatus;
+ // May be populated when mSecondaryProviderReportedStatus == PROVIDER_STATUS_IS_CERTAIN
+ // or PROVIDER_STATUS_IS_UNCERTAIN
+ @Nullable private final TimeZoneProviderStatus mSecondaryProviderReportedStatus;
+
+ public LocationTimeZoneAlgorithmStatus(
+ @DetectionAlgorithmStatus int status,
+ @ProviderStatus int primaryProviderStatus,
+ @Nullable TimeZoneProviderStatus primaryProviderReportedStatus,
+ @ProviderStatus int secondaryProviderStatus,
+ @Nullable TimeZoneProviderStatus secondaryProviderReportedStatus) {
+
+ mStatus = requireValidDetectionAlgorithmStatus(status);
+ mPrimaryProviderStatus = requireValidProviderStatus(primaryProviderStatus);
+ mPrimaryProviderReportedStatus = primaryProviderReportedStatus;
+ mSecondaryProviderStatus = requireValidProviderStatus(secondaryProviderStatus);
+ mSecondaryProviderReportedStatus = secondaryProviderReportedStatus;
+
+ boolean primaryProviderHasReported = hasProviderReported(primaryProviderStatus);
+ boolean primaryProviderReportedStatusPresent = primaryProviderReportedStatus != null;
+ if (!primaryProviderHasReported && primaryProviderReportedStatusPresent) {
+ throw new IllegalArgumentException(
+ "primaryProviderReportedStatus=" + primaryProviderReportedStatus
+ + ", primaryProviderStatus="
+ + providerStatusToString(primaryProviderStatus));
+ }
+
+ boolean secondaryProviderHasReported = hasProviderReported(secondaryProviderStatus);
+ boolean secondaryProviderReportedStatusPresent = secondaryProviderReportedStatus != null;
+ if (!secondaryProviderHasReported && secondaryProviderReportedStatusPresent) {
+ throw new IllegalArgumentException(
+ "secondaryProviderReportedStatus=" + secondaryProviderReportedStatus
+ + ", secondaryProviderStatus="
+ + providerStatusToString(secondaryProviderStatus));
+ }
+
+ // If the algorithm isn't running, providers can't report.
+ if (status != DETECTION_ALGORITHM_STATUS_RUNNING
+ && (primaryProviderHasReported || secondaryProviderHasReported)) {
+ throw new IllegalArgumentException(
+ "algorithmStatus=" + detectionAlgorithmStatusToString(status)
+ + ", primaryProviderReportedStatus=" + primaryProviderReportedStatus
+ + ", secondaryProviderReportedStatus="
+ + secondaryProviderReportedStatus);
+ }
+ }
+
+ /**
+ * Returns the status value of the detection algorithm.
+ */
+ public @DetectionAlgorithmStatus int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Returns the status of the primary location time zone provider as categorized by the detection
+ * algorithm.
+ */
+ public @ProviderStatus int getPrimaryProviderStatus() {
+ return mPrimaryProviderStatus;
+ }
+
+ /**
+ * Returns the status of the primary location time zone provider as reported by the provider
+ * itself. Can be {@code null} when the provider hasn't reported, or omitted when it has.
+ */
+ @Nullable
+ public TimeZoneProviderStatus getPrimaryProviderReportedStatus() {
+ return mPrimaryProviderReportedStatus;
+ }
+
+ /**
+ * Returns the status of the secondary location time zone provider as categorized by the
+ * detection algorithm.
+ */
+ public @ProviderStatus int getSecondaryProviderStatus() {
+ return mSecondaryProviderStatus;
+ }
+
+ /**
+ * Returns the status of the secondary location time zone provider as reported by the provider
+ * itself. Can be {@code null} when the provider hasn't reported, or omitted when it has.
+ */
+ @Nullable
+ public TimeZoneProviderStatus getSecondaryProviderReportedStatus() {
+ return mSecondaryProviderReportedStatus;
+ }
+
+ @Override
+ public String toString() {
+ return "LocationTimeZoneAlgorithmStatus{"
+ + "mAlgorithmStatus=" + detectionAlgorithmStatusToString(mStatus)
+ + ", mPrimaryProviderStatus=" + providerStatusToString(mPrimaryProviderStatus)
+ + ", mPrimaryProviderReportedStatus=" + mPrimaryProviderReportedStatus
+ + ", mSecondaryProviderStatus=" + providerStatusToString(mSecondaryProviderStatus)
+ + ", mSecondaryProviderReportedStatus=" + mSecondaryProviderReportedStatus
+ + '}';
+ }
+
+ /**
+ * Parses a {@link LocationTimeZoneAlgorithmStatus} from a toString() string for manual
+ * command-line testing.
+ */
+ @NonNull
+ public static LocationTimeZoneAlgorithmStatus parseCommandlineArg(@NonNull String arg) {
+ // Note: "}" has to be escaped on Android with "\\}" because the regexp library is not based
+ // on OpenJDK code.
+ Pattern pattern = Pattern.compile("LocationTimeZoneAlgorithmStatus\\{"
+ + "mAlgorithmStatus=(.+)"
+ + ", mPrimaryProviderStatus=([^,]+)"
+ + ", mPrimaryProviderReportedStatus=(null|TimeZoneProviderStatus\\{[^}]+\\})"
+ + ", mSecondaryProviderStatus=([^,]+)"
+ + ", mSecondaryProviderReportedStatus=(null|TimeZoneProviderStatus\\{[^}]+\\})"
+ + "\\}"
+ );
+ Matcher matcher = pattern.matcher(arg);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Unable to parse algorithm status arg: " + arg);
+ }
+ @DetectionAlgorithmStatus int algorithmStatus =
+ detectionAlgorithmStatusFromString(matcher.group(1));
+ @ProviderStatus int primaryProviderStatus = providerStatusFromString(matcher.group(2));
+ TimeZoneProviderStatus primaryProviderReportedStatus =
+ parseTimeZoneProviderStatusOrNull(matcher.group(3));
+ @ProviderStatus int secondaryProviderStatus = providerStatusFromString(matcher.group(4));
+ TimeZoneProviderStatus secondaryProviderReportedStatus =
+ parseTimeZoneProviderStatusOrNull(matcher.group(5));
+ return new LocationTimeZoneAlgorithmStatus(
+ algorithmStatus, primaryProviderStatus, primaryProviderReportedStatus,
+ secondaryProviderStatus, secondaryProviderReportedStatus);
+ }
+
+ @Nullable
+ private static TimeZoneProviderStatus parseTimeZoneProviderStatusOrNull(
+ String providerReportedStatusString) {
+ TimeZoneProviderStatus providerReportedStatus;
+ if ("null".equals(providerReportedStatusString)) {
+ providerReportedStatus = null;
+ } else {
+ providerReportedStatus =
+ TimeZoneProviderStatus.parseProviderStatus(providerReportedStatusString);
+ }
+ return providerReportedStatus;
+ }
+
+ @NonNull
+ public static final Creator<LocationTimeZoneAlgorithmStatus> CREATOR = new Creator<>() {
+ @Override
+ public LocationTimeZoneAlgorithmStatus createFromParcel(Parcel in) {
+ @DetectionAlgorithmStatus int algorithmStatus = in.readInt();
+ @ProviderStatus int primaryProviderStatus = in.readInt();
+ TimeZoneProviderStatus primaryProviderReportedStatus =
+ in.readParcelable(getClass().getClassLoader(), TimeZoneProviderStatus.class);
+ @ProviderStatus int secondaryProviderStatus = in.readInt();
+ TimeZoneProviderStatus secondaryProviderReportedStatus =
+ in.readParcelable(getClass().getClassLoader(), TimeZoneProviderStatus.class);
+ return new LocationTimeZoneAlgorithmStatus(
+ algorithmStatus, primaryProviderStatus, primaryProviderReportedStatus,
+ secondaryProviderStatus, secondaryProviderReportedStatus);
+ }
+
+ @Override
+ public LocationTimeZoneAlgorithmStatus[] newArray(int size) {
+ return new LocationTimeZoneAlgorithmStatus[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mStatus);
+ parcel.writeInt(mPrimaryProviderStatus);
+ parcel.writeParcelable(mPrimaryProviderReportedStatus, flags);
+ parcel.writeInt(mSecondaryProviderStatus);
+ parcel.writeParcelable(mSecondaryProviderReportedStatus, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ LocationTimeZoneAlgorithmStatus that = (LocationTimeZoneAlgorithmStatus) o;
+ return mStatus == that.mStatus
+ && mPrimaryProviderStatus == that.mPrimaryProviderStatus
+ && Objects.equals(
+ mPrimaryProviderReportedStatus, that.mPrimaryProviderReportedStatus)
+ && mSecondaryProviderStatus == that.mSecondaryProviderStatus
+ && Objects.equals(
+ mSecondaryProviderReportedStatus, that.mSecondaryProviderReportedStatus);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStatus,
+ mPrimaryProviderStatus, mPrimaryProviderReportedStatus,
+ mSecondaryProviderStatus, mSecondaryProviderReportedStatus);
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ @NonNull
+ public static String providerStatusToString(@ProviderStatus int providerStatus) {
+ switch (providerStatus) {
+ case PROVIDER_STATUS_NOT_PRESENT:
+ return "NOT_PRESENT";
+ case PROVIDER_STATUS_NOT_READY:
+ return "NOT_READY";
+ case PROVIDER_STATUS_IS_CERTAIN:
+ return "IS_CERTAIN";
+ case PROVIDER_STATUS_IS_UNCERTAIN:
+ return "IS_UNCERTAIN";
+ default:
+ throw new IllegalArgumentException("Unknown status: " + providerStatus);
+ }
+ }
+
+ /** @hide */
+ @VisibleForTesting public static @ProviderStatus int providerStatusFromString(
+ @Nullable String providerStatusString) {
+ if (TextUtils.isEmpty(providerStatusString)) {
+ throw new IllegalArgumentException("Empty status: " + providerStatusString);
+ }
+
+ switch (providerStatusString) {
+ case "NOT_PRESENT":
+ return PROVIDER_STATUS_NOT_PRESENT;
+ case "NOT_READY":
+ return PROVIDER_STATUS_NOT_READY;
+ case "IS_CERTAIN":
+ return PROVIDER_STATUS_IS_CERTAIN;
+ case "IS_UNCERTAIN":
+ return PROVIDER_STATUS_IS_UNCERTAIN;
+ default:
+ throw new IllegalArgumentException("Unknown status: " + providerStatusString);
+ }
+ }
+
+ private static boolean hasProviderReported(@ProviderStatus int providerStatus) {
+ return providerStatus == PROVIDER_STATUS_IS_CERTAIN
+ || providerStatus == PROVIDER_STATUS_IS_UNCERTAIN;
+ }
+
+ /** @hide */
+ @VisibleForTesting public static @ProviderStatus int requireValidProviderStatus(
+ @ProviderStatus int providerStatus) {
+ if (providerStatus < PROVIDER_STATUS_NOT_PRESENT
+ || providerStatus > PROVIDER_STATUS_IS_UNCERTAIN) {
+ throw new IllegalArgumentException(
+ "Invalid provider status: " + providerStatus);
+ }
+ return providerStatus;
+ }
+}
diff --git a/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl
new file mode 100644
index 000000000000..0eb5b63b7ffb
--- /dev/null
+++ b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.app.time;
+
+parcelable TelephonyTimeZoneAlgorithmStatus;
diff --git a/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java
new file mode 100644
index 000000000000..95240c00fa3f
--- /dev/null
+++ b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java
@@ -0,0 +1,96 @@
+/*
+ * 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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusToString;
+import static android.app.time.DetectorStatusTypes.requireValidDetectionAlgorithmStatus;
+
+import android.annotation.NonNull;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Information about the status of the telephony-based time zone detection algorithm.
+ *
+ * @hide
+ */
+public final class TelephonyTimeZoneAlgorithmStatus implements Parcelable {
+
+ private final @DetectionAlgorithmStatus int mAlgorithmStatus;
+
+ public TelephonyTimeZoneAlgorithmStatus(@DetectionAlgorithmStatus int algorithmStatus) {
+ mAlgorithmStatus = requireValidDetectionAlgorithmStatus(algorithmStatus);
+ }
+
+ /**
+ * Returns the status of the detection algorithm.
+ */
+ public @DetectionAlgorithmStatus int getAlgorithmStatus() {
+ return mAlgorithmStatus;
+ }
+
+ @Override
+ public String toString() {
+ return "TelephonyTimeZoneAlgorithmStatus{"
+ + "mAlgorithmStatus=" + detectionAlgorithmStatusToString(mAlgorithmStatus)
+ + '}';
+ }
+
+ @NonNull
+ public static final Creator<TelephonyTimeZoneAlgorithmStatus> CREATOR = new Creator<>() {
+ @Override
+ public TelephonyTimeZoneAlgorithmStatus createFromParcel(Parcel in) {
+ @DetectionAlgorithmStatus int algorithmStatus = in.readInt();
+ return new TelephonyTimeZoneAlgorithmStatus(algorithmStatus);
+ }
+
+ @Override
+ public TelephonyTimeZoneAlgorithmStatus[] newArray(int size) {
+ return new TelephonyTimeZoneAlgorithmStatus[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mAlgorithmStatus);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TelephonyTimeZoneAlgorithmStatus that = (TelephonyTimeZoneAlgorithmStatus) o;
+ return mAlgorithmStatus == that.mAlgorithmStatus;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAlgorithmStatus);
+ }
+}
diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
index cd91b0431b28..4684c6ad811c 100644
--- a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
@@ -23,27 +23,40 @@ import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
+import java.util.concurrent.Executor;
/**
- * A pair containing a user's {@link TimeZoneCapabilities} and {@link TimeZoneConfiguration}.
+ * An object containing a user's {@link TimeZoneCapabilities} and {@link TimeZoneConfiguration}.
*
* @hide
*/
@SystemApi
public final class TimeZoneCapabilitiesAndConfig implements Parcelable {
- public static final @NonNull Creator<TimeZoneCapabilitiesAndConfig> CREATOR =
- new Creator<TimeZoneCapabilitiesAndConfig>() {
- public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
- return TimeZoneCapabilitiesAndConfig.createFromParcel(in);
- }
-
- public TimeZoneCapabilitiesAndConfig[] newArray(int size) {
- return new TimeZoneCapabilitiesAndConfig[size];
- }
- };
+ public static final @NonNull Creator<TimeZoneCapabilitiesAndConfig> CREATOR = new Creator<>() {
+ public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
+ return TimeZoneCapabilitiesAndConfig.createFromParcel(in);
+ }
+ public TimeZoneCapabilitiesAndConfig[] newArray(int size) {
+ return new TimeZoneCapabilitiesAndConfig[size];
+ }
+ };
+ /**
+ * The time zone detector status.
+ *
+ * Implementation note for future platform engineers: This field is only needed by SettingsUI
+ * initially and so it has not been added to the SDK API. {@link TimeZoneDetectorStatus}
+ * contains details about the internals of the time zone detector so thought should be given to
+ * abstraction / exposing a lightweight version if something unbundled needs access to detector
+ * details. Also, that could be good time to add separate APIs for bundled components, or add
+ * new APIs that return something more extensible and generic like a Bundle or a less
+ * constraining name. See also {@link
+ * TimeManager#addTimeZoneDetectorListener(Executor, TimeManager.TimeZoneDetectorListener)},
+ * which notified of changes to any fields in this class, including the detector status.
+ */
+ @NonNull private final TimeZoneDetectorStatus mDetectorStatus;
@NonNull private final TimeZoneCapabilities mCapabilities;
@NonNull private final TimeZoneConfiguration mConfiguration;
@@ -53,26 +66,41 @@ public final class TimeZoneCapabilitiesAndConfig implements Parcelable {
* @hide
*/
public TimeZoneCapabilitiesAndConfig(
+ @NonNull TimeZoneDetectorStatus detectorStatus,
@NonNull TimeZoneCapabilities capabilities,
@NonNull TimeZoneConfiguration configuration) {
- this.mCapabilities = Objects.requireNonNull(capabilities);
- this.mConfiguration = Objects.requireNonNull(configuration);
+ mDetectorStatus = Objects.requireNonNull(detectorStatus);
+ mCapabilities = Objects.requireNonNull(capabilities);
+ mConfiguration = Objects.requireNonNull(configuration);
}
@NonNull
private static TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
- TimeZoneCapabilities capabilities = in.readParcelable(null, android.app.time.TimeZoneCapabilities.class);
- TimeZoneConfiguration configuration = in.readParcelable(null, android.app.time.TimeZoneConfiguration.class);
- return new TimeZoneCapabilitiesAndConfig(capabilities, configuration);
+ TimeZoneDetectorStatus detectorStatus =
+ in.readParcelable(null, TimeZoneDetectorStatus.class);
+ TimeZoneCapabilities capabilities = in.readParcelable(null, TimeZoneCapabilities.class);
+ TimeZoneConfiguration configuration = in.readParcelable(null, TimeZoneConfiguration.class);
+ return new TimeZoneCapabilitiesAndConfig(detectorStatus, capabilities, configuration);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mDetectorStatus, flags);
dest.writeParcelable(mCapabilities, flags);
dest.writeParcelable(mConfiguration, flags);
}
/**
+ * Returns the time zone detector's status.
+ *
+ * @hide
+ */
+ @NonNull
+ public TimeZoneDetectorStatus getDetectorStatus() {
+ return mDetectorStatus;
+ }
+
+ /**
* Returns the user's time zone behavior capabilities.
*/
@NonNull
@@ -102,7 +130,8 @@ public final class TimeZoneCapabilitiesAndConfig implements Parcelable {
return false;
}
TimeZoneCapabilitiesAndConfig that = (TimeZoneCapabilitiesAndConfig) o;
- return mCapabilities.equals(that.mCapabilities)
+ return mDetectorStatus.equals(that.mDetectorStatus)
+ && mCapabilities.equals(that.mCapabilities)
&& mConfiguration.equals(that.mConfiguration);
}
@@ -114,7 +143,8 @@ public final class TimeZoneCapabilitiesAndConfig implements Parcelable {
@Override
public String toString() {
return "TimeZoneCapabilitiesAndConfig{"
- + "mCapabilities=" + mCapabilities
+ + "mDetectorStatus=" + mDetectorStatus
+ + ", mCapabilities=" + mCapabilities
+ ", mConfiguration=" + mConfiguration
+ '}';
}
diff --git a/core/java/android/app/time/TimeZoneDetectorStatus.aidl b/core/java/android/app/time/TimeZoneDetectorStatus.aidl
new file mode 100644
index 000000000000..32204df6d698
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneDetectorStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.app.time;
+
+parcelable TimeZoneDetectorStatus;
diff --git a/core/java/android/app/time/TimeZoneDetectorStatus.java b/core/java/android/app/time/TimeZoneDetectorStatus.java
new file mode 100644
index 000000000000..16374639b77c
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneDetectorStatus.java
@@ -0,0 +1,124 @@
+/*
+ * 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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DetectorStatus;
+import static android.app.time.DetectorStatusTypes.requireValidDetectorStatus;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Information about the status of the automatic time zone detector. Used by SettingsUI to display
+ * status information to the user.
+ *
+ * @hide
+ */
+public final class TimeZoneDetectorStatus implements Parcelable {
+
+ private final @DetectorStatus int mDetectorStatus;
+ @NonNull private final TelephonyTimeZoneAlgorithmStatus mTelephonyTimeZoneAlgorithmStatus;
+ @NonNull private final LocationTimeZoneAlgorithmStatus mLocationTimeZoneAlgorithmStatus;
+
+ public TimeZoneDetectorStatus(
+ @DetectorStatus int detectorStatus,
+ @NonNull TelephonyTimeZoneAlgorithmStatus telephonyTimeZoneAlgorithmStatus,
+ @NonNull LocationTimeZoneAlgorithmStatus locationTimeZoneAlgorithmStatus) {
+ mDetectorStatus = requireValidDetectorStatus(detectorStatus);
+ mTelephonyTimeZoneAlgorithmStatus =
+ Objects.requireNonNull(telephonyTimeZoneAlgorithmStatus);
+ mLocationTimeZoneAlgorithmStatus = Objects.requireNonNull(locationTimeZoneAlgorithmStatus);
+ }
+
+ public @DetectorStatus int getDetectorStatus() {
+ return mDetectorStatus;
+ }
+
+ @NonNull
+ public TelephonyTimeZoneAlgorithmStatus getTelephonyTimeZoneAlgorithmStatus() {
+ return mTelephonyTimeZoneAlgorithmStatus;
+ }
+
+ @NonNull
+ public LocationTimeZoneAlgorithmStatus getLocationTimeZoneAlgorithmStatus() {
+ return mLocationTimeZoneAlgorithmStatus;
+ }
+
+ @Override
+ public String toString() {
+ return "TimeZoneDetectorStatus{"
+ + "mDetectorStatus=" + DetectorStatusTypes.detectorStatusToString(mDetectorStatus)
+ + ", mTelephonyTimeZoneAlgorithmStatus=" + mTelephonyTimeZoneAlgorithmStatus
+ + ", mLocationTimeZoneAlgorithmStatus=" + mLocationTimeZoneAlgorithmStatus
+ + '}';
+ }
+
+ public static final @NonNull Creator<TimeZoneDetectorStatus> CREATOR = new Creator<>() {
+ @Override
+ public TimeZoneDetectorStatus createFromParcel(Parcel in) {
+ @DetectorStatus int detectorStatus = in.readInt();
+ TelephonyTimeZoneAlgorithmStatus telephonyTimeZoneAlgorithmStatus =
+ in.readParcelable(getClass().getClassLoader(),
+ TelephonyTimeZoneAlgorithmStatus.class);
+ LocationTimeZoneAlgorithmStatus locationTimeZoneAlgorithmStatus =
+ in.readParcelable(getClass().getClassLoader(),
+ LocationTimeZoneAlgorithmStatus.class);
+ return new TimeZoneDetectorStatus(detectorStatus,
+ telephonyTimeZoneAlgorithmStatus, locationTimeZoneAlgorithmStatus);
+ }
+
+ @Override
+ public TimeZoneDetectorStatus[] newArray(int size) {
+ return new TimeZoneDetectorStatus[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mDetectorStatus);
+ parcel.writeParcelable(mTelephonyTimeZoneAlgorithmStatus, flags);
+ parcel.writeParcelable(mLocationTimeZoneAlgorithmStatus, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeZoneDetectorStatus that = (TimeZoneDetectorStatus) o;
+ return mDetectorStatus == that.mDetectorStatus
+ && mTelephonyTimeZoneAlgorithmStatus.equals(that.mTelephonyTimeZoneAlgorithmStatus)
+ && mLocationTimeZoneAlgorithmStatus.equals(that.mLocationTimeZoneAlgorithmStatus);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDetectorStatus, mTelephonyTimeZoneAlgorithmStatus,
+ mLocationTimeZoneAlgorithmStatus);
+ }
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index 0e9e28be8818..f357fb243fe1 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -81,11 +81,11 @@ public interface TimeZoneDetector {
String SHELL_COMMAND_SET_GEO_DETECTION_ENABLED = "set_geo_detection_enabled";
/**
- * A shell command that injects a geolocation time zone suggestion (as if from the
+ * A shell command that injects a location algorithm event (as if from the
* location_time_zone_manager).
* @hide
*/
- String SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE = "suggest_geo_location_time_zone";
+ String SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT = "handle_location_algorithm_event";
/**
* A shell command that injects a manual time zone suggestion (as if from the SettingsUI or
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 6eeb5e7f4b0f..8a09cd77efdb 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -25,9 +25,6 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.app.ActivityThread;
-import android.content.ContentResolver;
-import android.content.Context;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.Uri;
@@ -882,9 +879,8 @@ public final class DeviceConfig {
@NonNull
@RequiresPermission(READ_DEVICE_CONFIG)
public static Properties getProperties(@NonNull String namespace, @NonNull String ... names) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
return new Properties(namespace,
- Settings.Config.getStrings(contentResolver, namespace, Arrays.asList(names)));
+ Settings.Config.getStrings(namespace, Arrays.asList(names)));
}
/**
@@ -1023,8 +1019,7 @@ public final class DeviceConfig {
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static boolean setProperty(@NonNull String namespace, @NonNull String name,
@Nullable String value, boolean makeDefault) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- return Settings.Config.putString(contentResolver, namespace, name, value, makeDefault);
+ return Settings.Config.putString(namespace, name, value, makeDefault);
}
/**
@@ -1045,8 +1040,7 @@ public final class DeviceConfig {
@SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static boolean setProperties(@NonNull Properties properties) throws BadConfigException {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- return Settings.Config.setStrings(contentResolver, properties.getNamespace(),
+ return Settings.Config.setStrings(properties.getNamespace(),
properties.mMap);
}
@@ -1062,8 +1056,7 @@ public final class DeviceConfig {
@SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static boolean deleteProperty(@NonNull String namespace, @NonNull String name) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- return Settings.Config.deleteString(contentResolver, namespace, name);
+ return Settings.Config.deleteString(namespace, name);
}
/**
@@ -1094,8 +1087,7 @@ public final class DeviceConfig {
@SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static void resetToDefaults(@ResetMode int resetMode, @Nullable String namespace) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- Settings.Config.resetToDefaults(contentResolver, resetMode, namespace);
+ Settings.Config.resetToDefaults(resetMode, namespace);
}
/**
@@ -1112,8 +1104,7 @@ public final class DeviceConfig {
*/
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static void setSyncDisabledMode(@SyncDisabledMode int syncDisabledMode) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- Settings.Config.setSyncDisabledMode(contentResolver, syncDisabledMode);
+ Settings.Config.setSyncDisabledMode(syncDisabledMode);
}
/**
@@ -1124,8 +1115,7 @@ public final class DeviceConfig {
*/
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static @SyncDisabledMode int getSyncDisabledMode() {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- return Settings.Config.getSyncDisabledMode(contentResolver);
+ return Settings.Config.getSyncDisabledMode();
}
/**
@@ -1148,8 +1138,7 @@ public final class DeviceConfig {
@NonNull String namespace,
@NonNull @CallbackExecutor Executor executor,
@NonNull OnPropertiesChangedListener onPropertiesChangedListener) {
- enforceReadPermission(ActivityThread.currentApplication().getApplicationContext(),
- namespace);
+ enforceReadPermission(namespace);
synchronized (sLock) {
Pair<String, Executor> oldNamespace = sListeners.get(onPropertiesChangedListener);
if (oldNamespace == null) {
@@ -1216,7 +1205,7 @@ public final class DeviceConfig {
}
}
};
- ActivityThread.currentApplication().getContentResolver()
+ Settings.Config
.registerContentObserver(createNamespaceUri(namespace), true, contentObserver);
sNamespaces.put(namespace, new Pair<>(contentObserver, 1));
}
@@ -1240,8 +1229,7 @@ public final class DeviceConfig {
sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second - 1));
} else {
// Decrementing a namespace to zero means we no longer need its ContentObserver.
- ActivityThread.currentApplication().getContentResolver()
- .unregisterContentObserver(namespaceCount.first);
+ Settings.Config.unregisterContentObserver(namespaceCount.first);
sNamespaces.remove(namespace);
}
}
@@ -1281,8 +1269,8 @@ public final class DeviceConfig {
* Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces.
* @hide
*/
- public static void enforceReadPermission(Context context, String namespace) {
- if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+ public static void enforceReadPermission(String namespace) {
+ if (Settings.Config.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
!= PackageManager.PERMISSION_GRANTED) {
if (!PUBLIC_NAMESPACES.contains(namespace)) {
throw new SecurityException("Permission denial: reading from settings requires:"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 52b1adb876fc..ef448f5dbd1b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -47,9 +47,11 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PermissionName;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.database.Cursor;
import android.database.SQLException;
import android.location.ILocationManager;
@@ -3344,7 +3346,7 @@ public final class Settings {
public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
List<String> names) {
String namespace = prefix.substring(0, prefix.length() - 1);
- DeviceConfig.enforceReadPermission(ActivityThread.currentApplication(), namespace);
+ DeviceConfig.enforceReadPermission(namespace);
ArrayMap<String, String> keyValues = new ArrayMap<>();
int currentGeneration = -1;
@@ -18002,20 +18004,36 @@ public final class Settings {
/**
* Look up a name in the database.
- * @param resolver to access the database with
* @param name to look up in the table
* @return the corresponding value, or null if not present
*
* @hide
*/
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
- static String getString(ContentResolver resolver, String name) {
+ static String getString(String name) {
+ ContentResolver resolver = getContentResolver();
return sNameValueCache.getStringForUser(resolver, name, resolver.getUserId());
}
/**
* Look up a list of names in the database, within the specified namespace.
*
+ * @param namespace to which the names belong
+ * @param names to look up in the table
+ * @return a non null, but possibly empty, map from name to value for any of the names that
+ * were found during lookup.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+ public static Map<String, String> getStrings(@NonNull String namespace,
+ @NonNull List<String> names) {
+ return getStrings(getContentResolver(), namespace, names);
+ }
+
+ /**
+ * Look up a list of names in the database, within the specified namespace.
+ *
* @param resolver to access the database with
* @param namespace to which the names belong
* @param names to look up in the table
@@ -18053,7 +18071,6 @@ public final class Settings {
* <strong>not</strong> be set as the default.
* </p>
*
- * @param resolver to access the database with.
* @param namespace to store the name/value pair in.
* @param name to store.
* @param value to associate with the name.
@@ -18065,8 +18082,9 @@ public final class Settings {
* @hide
*/
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static boolean putString(@NonNull ContentResolver resolver, @NonNull String namespace,
+ public static boolean putString(@NonNull String namespace,
@NonNull String name, @Nullable String value, boolean makeDefault) {
+ ContentResolver resolver = getContentResolver();
return sNameValueCache.putStringForUser(resolver, createCompositeName(namespace, name),
value, null, makeDefault, resolver.getUserId(),
DEFAULT_OVERRIDEABLE_BY_RESTORE);
@@ -18076,6 +18094,23 @@ public final class Settings {
* Clear all name/value pairs for the provided namespace and save new name/value pairs in
* their place.
*
+ * @param namespace to which the names should be set.
+ * @param keyValues map of key names (without the prefix) to values.
+ * @return true if the name/value pairs were set, false if setting was blocked
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
+ public static boolean setStrings(@NonNull String namespace,
+ @NonNull Map<String, String> keyValues)
+ throws DeviceConfig.BadConfigException {
+ return setStrings(getContentResolver(), namespace, keyValues);
+ }
+
+ /**
+ * Clear all name/value pairs for the provided namespace and save new name/value pairs in
+ * their place.
+ *
* @param resolver to access the database with.
* @param namespace to which the names should be set.
* @param keyValues map of key names (without the prefix) to values.
@@ -18106,7 +18141,6 @@ public final class Settings {
/**
* Delete a name/value pair from the database for the specified namespace.
*
- * @param resolver to access the database with.
* @param namespace to delete the name/value pair from.
* @param name to delete.
* @return true if the value was deleted, false on database errors. If the name/value pair
@@ -18117,8 +18151,9 @@ public final class Settings {
* @hide
*/
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static boolean deleteString(@NonNull ContentResolver resolver, @NonNull String namespace,
+ static boolean deleteString(@NonNull String namespace,
@NonNull String name) {
+ ContentResolver resolver = getContentResolver();
return sNameValueCache.deleteStringForUser(resolver,
createCompositeName(namespace, name), resolver.getUserId());
}
@@ -18129,7 +18164,6 @@ public final class Settings {
* The method accepts an optional prefix parameter. If provided, only pairs with a name that
* starts with the exact prefix will be reset. Otherwise all will be reset.
*
- * @param resolver Handle to the content resolver.
* @param resetMode The reset mode to use.
* @param namespace Optionally, to limit which which namespace is reset.
*
@@ -18138,9 +18172,10 @@ public final class Settings {
* @hide
*/
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static void resetToDefaults(@NonNull ContentResolver resolver, @ResetMode int resetMode,
+ static void resetToDefaults(@ResetMode int resetMode,
@Nullable String namespace) {
try {
+ ContentResolver resolver = getContentResolver();
Bundle arg = new Bundle();
arg.putInt(CALL_METHOD_USER_KEY, resolver.getUserId());
arg.putInt(CALL_METHOD_RESET_MODE_KEY, resetMode);
@@ -18163,9 +18198,9 @@ public final class Settings {
*/
@SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static void setSyncDisabledMode(
- @NonNull ContentResolver resolver, @SyncDisabledMode int disableSyncMode) {
+ static void setSyncDisabledMode(@SyncDisabledMode int disableSyncMode) {
try {
+ ContentResolver resolver = getContentResolver();
Bundle args = new Bundle();
args.putInt(CALL_METHOD_SYNC_DISABLED_MODE_KEY, disableSyncMode);
IContentProvider cp = sProviderHolder.getProvider(resolver);
@@ -18184,8 +18219,9 @@ public final class Settings {
*/
@SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static int getSyncDisabledMode(@NonNull ContentResolver resolver) {
+ static int getSyncDisabledMode() {
try {
+ ContentResolver resolver = getContentResolver();
Bundle args = Bundle.EMPTY;
IContentProvider cp = sProviderHolder.getProvider(resolver);
Bundle bundle = cp.call(resolver.getAttributionSource(),
@@ -18202,7 +18238,6 @@ public final class Settings {
/**
* Register callback for monitoring Config table.
*
- * @param resolver Handle to the content resolver.
* @param callback callback to register
*
* @hide
@@ -18213,6 +18248,50 @@ public final class Settings {
registerMonitorCallbackAsUser(resolver, resolver.getUserId(), callback);
}
+
+ /**
+ * Register a content observer
+ *
+ * @hide
+ */
+ public static void registerContentObserver(@NonNull Uri uri, boolean notifyForDescendants,
+ @NonNull ContentObserver observer) {
+ ActivityThread.currentApplication().getContentResolver()
+ .registerContentObserver(uri, notifyForDescendants, observer);
+ }
+
+ /**
+ * Unregister a content observer
+ *
+ * @hide
+ */
+ public static void unregisterContentObserver(@NonNull ContentObserver observer) {
+ ActivityThread.currentApplication().getContentResolver()
+ .unregisterContentObserver(observer);
+ }
+
+ /**
+ * Determine whether the calling process of an IPC <em>or you</em> have been
+ * granted a particular permission. This is the same as
+ * {@link #checkCallingPermission}, except it grants your own permissions
+ * if you are not currently processing an IPC. Use with care!
+ *
+ * @param permission The name of the permission being checked.
+ *
+ * @return {@link PackageManager#PERMISSION_GRANTED} if the calling
+ * pid/uid is allowed that permission, or
+ * {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see PackageManager#checkPermission(String, String)
+ * @see #checkPermission
+ * @see #checkCallingPermission
+ * @hide
+ */
+ public static int checkCallingOrSelfPermission(@NonNull @PermissionName String permission) {
+ return ActivityThread.currentApplication()
+ .getApplicationContext().checkCallingOrSelfPermission(permission);
+ }
+
private static void registerMonitorCallbackAsUser(
@NonNull ContentResolver resolver, @UserIdInt int userHandle,
@NonNull RemoteCallback callback) {
@@ -18245,6 +18324,10 @@ public final class Settings {
Preconditions.checkNotNull(namespace);
return namespace + "/";
}
+
+ private static ContentResolver getContentResolver() {
+ return ActivityThread.currentApplication().getContentResolver();
+ }
}
/**
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
index 1df4fc54089e..500c41cd1fed 100644
--- a/core/java/android/view/inputmethod/TextAppearanceInfo.java
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -16,12 +16,13 @@
package android.view.inputmethod;
+import static android.graphics.Typeface.NORMAL;
+
import android.annotation.ColorInt;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
-import android.content.res.ColorStateList;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.fonts.FontStyle;
@@ -30,7 +31,7 @@ import android.inputmethodservice.InputMethodService;
import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.InputFilter;
+import android.text.method.TransformationMethod;
import android.widget.TextView;
import java.util.Objects;
@@ -38,7 +39,6 @@ import java.util.Objects;
/**
* Information about text appearance in an editor, passed through
* {@link CursorAnchorInfo} for use by {@link InputMethodService}.
- *
* @see TextView
* @see Paint
* @see CursorAnchorInfo.Builder#setTextAppearanceInfo(TextAppearanceInfo)
@@ -46,12 +46,12 @@ import java.util.Objects;
*/
public final class TextAppearanceInfo implements Parcelable {
/**
- * The text size (in pixels) for current {@link TextView}.
+ * The text size (in pixels) for current editor.
*/
private final @Px float mTextSize;
/**
- * The LocaleList of the text.
+ * The {@link LocaleList} of the text.
*/
@NonNull private final LocaleList mTextLocales;
@@ -64,7 +64,8 @@ public final class TextAppearanceInfo implements Parcelable {
/**
* The weight of the text.
*/
- private final @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int mTextFontWeight;
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+ private final int mTextFontWeight;
/**
* The style (normal, bold, italic, bold|italic) of the text, see {@link Typeface}.
@@ -72,8 +73,7 @@ public final class TextAppearanceInfo implements Parcelable {
private final @Typeface.Style int mTextStyle;
/**
- * Whether the transformation method applied to the current {@link TextView} is set to
- * ALL CAPS.
+ * Whether the transformation method applied to the current editor is set to all caps.
*/
private final boolean mAllCaps;
@@ -93,6 +93,11 @@ public final class TextAppearanceInfo implements Parcelable {
private final @Px float mShadowRadius;
/**
+ * The shadow color of the text shadow.
+ */
+ private final @ColorInt int mShadowColor;
+
+ /**
* The elegant text height, especially for less compacted complex script text.
*/
private final boolean mElegantTextHeight;
@@ -135,67 +140,46 @@ public final class TextAppearanceInfo implements Parcelable {
/**
* The color of the text selection highlight.
*/
- private final @ColorInt int mTextColorHighlight;
+ private final @ColorInt int mHighlightTextColor;
/**
- * The current text color.
+ * The current text color of the editor.
*/
private final @ColorInt int mTextColor;
/**
- * The current color of the hint text.
- */
- private final @ColorInt int mTextColorHint;
-
- /**
- * The text color for links.
- */
- @Nullable private final ColorStateList mTextColorLink;
-
- /**
- * The max length of text.
- */
- private final int mMaxLength;
-
-
- public TextAppearanceInfo(@NonNull TextView textView) {
- mTextSize = textView.getTextSize();
- mTextLocales = textView.getTextLocales();
- Typeface typeface = textView.getPaint().getTypeface();
- String systemFontFamilyName = null;
- int textFontWeight = -1;
- if (typeface != null) {
- systemFontFamilyName = typeface.getSystemFontFamilyName();
- textFontWeight = typeface.getWeight();
- }
- mSystemFontFamilyName = systemFontFamilyName;
- mTextFontWeight = textFontWeight;
- mTextStyle = textView.getTypefaceStyle();
- mAllCaps = textView.isAllCaps();
- mShadowRadius = textView.getShadowRadius();
- mShadowDx = textView.getShadowDx();
- mShadowDy = textView.getShadowDy();
- mElegantTextHeight = textView.isElegantTextHeight();
- mFallbackLineSpacing = textView.isFallbackLineSpacing();
- mLetterSpacing = textView.getLetterSpacing();
- mFontFeatureSettings = textView.getFontFeatureSettings();
- mFontVariationSettings = textView.getFontVariationSettings();
- mLineBreakStyle = textView.getLineBreakStyle();
- mLineBreakWordStyle = textView.getLineBreakWordStyle();
- mTextScaleX = textView.getTextScaleX();
- mTextColorHighlight = textView.getHighlightColor();
- mTextColor = textView.getCurrentTextColor();
- mTextColorHint = textView.getCurrentHintTextColor();
- mTextColorLink = textView.getLinkTextColors();
- int maxLength = -1;
- for (InputFilter filter: textView.getFilters()) {
- if (filter instanceof InputFilter.LengthFilter) {
- maxLength = ((InputFilter.LengthFilter) filter).getMax();
- // There is at most one LengthFilter.
- break;
- }
- }
- mMaxLength = maxLength;
+ * The current color of the hint text.
+ */
+ private final @ColorInt int mHintTextColor;
+
+ /**
+ * The text color used to paint the links in the editor.
+ */
+ private final @ColorInt int mLinkTextColor;
+
+ private TextAppearanceInfo(@NonNull final TextAppearanceInfo.Builder builder) {
+ mTextSize = builder.mTextSize;
+ mTextLocales = builder.mTextLocales;
+ mSystemFontFamilyName = builder.mSystemFontFamilyName;
+ mTextFontWeight = builder.mTextFontWeight;
+ mTextStyle = builder.mTextStyle;
+ mAllCaps = builder.mAllCaps;
+ mShadowDx = builder.mShadowDx;
+ mShadowDy = builder.mShadowDy;
+ mShadowRadius = builder.mShadowRadius;
+ mShadowColor = builder.mShadowColor;
+ mElegantTextHeight = builder.mElegantTextHeight;
+ mFallbackLineSpacing = builder.mFallbackLineSpacing;
+ mLetterSpacing = builder.mLetterSpacing;
+ mFontFeatureSettings = builder.mFontFeatureSettings;
+ mFontVariationSettings = builder.mFontVariationSettings;
+ mLineBreakStyle = builder.mLineBreakStyle;
+ mLineBreakWordStyle = builder.mLineBreakWordStyle;
+ mTextScaleX = builder.mTextScaleX;
+ mHighlightTextColor = builder.mHighlightTextColor;
+ mTextColor = builder.mTextColor;
+ mHintTextColor = builder.mHintTextColor;
+ mLinkTextColor = builder.mLinkTextColor;
}
@Override
@@ -214,6 +198,7 @@ public final class TextAppearanceInfo implements Parcelable {
dest.writeFloat(mShadowDx);
dest.writeFloat(mShadowDy);
dest.writeFloat(mShadowRadius);
+ dest.writeInt(mShadowColor);
dest.writeBoolean(mElegantTextHeight);
dest.writeBoolean(mFallbackLineSpacing);
dest.writeFloat(mLetterSpacing);
@@ -222,14 +207,13 @@ public final class TextAppearanceInfo implements Parcelable {
dest.writeInt(mLineBreakStyle);
dest.writeInt(mLineBreakWordStyle);
dest.writeFloat(mTextScaleX);
- dest.writeInt(mTextColorHighlight);
+ dest.writeInt(mHighlightTextColor);
dest.writeInt(mTextColor);
- dest.writeInt(mTextColorHint);
- dest.writeTypedObject(mTextColorLink, flags);
- dest.writeInt(mMaxLength);
+ dest.writeInt(mHintTextColor);
+ dest.writeInt(mLinkTextColor);
}
- private TextAppearanceInfo(@NonNull Parcel in) {
+ TextAppearanceInfo(@NonNull Parcel in) {
mTextSize = in.readFloat();
mTextLocales = LocaleList.CREATOR.createFromParcel(in);
mAllCaps = in.readBoolean();
@@ -239,6 +223,7 @@ public final class TextAppearanceInfo implements Parcelable {
mShadowDx = in.readFloat();
mShadowDy = in.readFloat();
mShadowRadius = in.readFloat();
+ mShadowColor = in.readInt();
mElegantTextHeight = in.readBoolean();
mFallbackLineSpacing = in.readBoolean();
mLetterSpacing = in.readFloat();
@@ -247,11 +232,10 @@ public final class TextAppearanceInfo implements Parcelable {
mLineBreakStyle = in.readInt();
mLineBreakWordStyle = in.readInt();
mTextScaleX = in.readFloat();
- mTextColorHighlight = in.readInt();
+ mHighlightTextColor = in.readInt();
mTextColor = in.readInt();
- mTextColorHint = in.readInt();
- mTextColorLink = in.readTypedObject(ColorStateList.CREATOR);
- mMaxLength = in.readInt();
+ mHintTextColor = in.readInt();
+ mLinkTextColor = in.readInt();
}
@NonNull
@@ -268,14 +252,14 @@ public final class TextAppearanceInfo implements Parcelable {
};
/**
- * Returns the text size (in pixels) for current {@link TextView}.
+ * Returns the text size (in pixels) for current editor.
*/
public @Px float getTextSize() {
return mTextSize;
}
/**
- * Returns the LocaleList of the text.
+ * Returns the {@link LocaleList} of the text.
*/
@NonNull
public LocaleList getTextLocales() {
@@ -286,31 +270,38 @@ public final class TextAppearanceInfo implements Parcelable {
* Returns the font family name if the {@link Typeface} of the text is created from a
* system font family. Returns null if no {@link Typeface} is specified, or it is not created
* from a system font family.
+ *
+ * @see Typeface#getSystemFontFamilyName()
*/
@Nullable
- public String getFontFamilyName() {
+ public String getSystemFontFamilyName() {
return mSystemFontFamilyName;
}
/**
- * Returns the weight of the text. Returns -1 when no {@link Typeface} is specified.
+ * Returns the weight of the text, or {@code FontStyle#FONT_WEIGHT_UNSPECIFIED}
+ * when no {@link Typeface} is specified.
*/
- public @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int getTextFontWeight() {
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+ public int getTextFontWeight() {
return mTextFontWeight;
}
/**
* Returns the style (normal, bold, italic, bold|italic) of the text. Returns
- * {@link Typeface#NORMAL} when no {@link Typeface} is specified. See {@link Typeface} for
- * more information.
+ * {@link Typeface#NORMAL} when no {@link Typeface} is specified.
+ *
+ * @see Typeface
*/
public @Typeface.Style int getTextStyle() {
return mTextStyle;
}
/**
- * Returns whether the transformation method applied to the current {@link TextView} is set to
- * ALL CAPS.
+ * Returns whether the transformation method applied to the current editor is set to all caps.
+ *
+ * @see TextView#setAllCaps(boolean)
+ * @see TextView#setTransformationMethod(TransformationMethod)
*/
public boolean isAllCaps() {
return mAllCaps;
@@ -318,6 +309,8 @@ public final class TextAppearanceInfo implements Parcelable {
/**
* Returns the horizontal offset (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
*/
public @Px float getShadowDx() {
return mShadowDx;
@@ -325,6 +318,8 @@ public final class TextAppearanceInfo implements Parcelable {
/**
* Returns the vertical offset (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
*/
public @Px float getShadowDy() {
return mShadowDy;
@@ -332,15 +327,28 @@ public final class TextAppearanceInfo implements Parcelable {
/**
* Returns the blur radius (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
*/
public @Px float getShadowRadius() {
return mShadowRadius;
}
/**
+ * Returns the color of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ public @ColorInt int getShadowColor() {
+ return mShadowColor;
+ }
+
+ /**
* Returns {@code true} if the elegant height metrics flag is set. This setting selects font
* variants that have not been compacted to fit Latin-based vertical metrics, and also increases
* top and bottom bounds to provide more space.
+ *
+ * @see Paint#isElegantTextHeight()
*/
public boolean isElegantTextHeight() {
return mElegantTextHeight;
@@ -411,13 +419,17 @@ public final class TextAppearanceInfo implements Parcelable {
/**
* Returns the color of the text selection highlight.
+ *
+ * @see TextView#getHighlightColor()
*/
- public @ColorInt int getTextColorHighlight() {
- return mTextColorHighlight;
+ public @ColorInt int getHighlightTextColor() {
+ return mHighlightTextColor;
}
/**
- * Returns the current text color.
+ * Returns the current text color of the editor.
+ *
+ * @see TextView#getCurrentTextColor()
*/
public @ColorInt int getTextColor() {
return mTextColor;
@@ -425,27 +437,22 @@ public final class TextAppearanceInfo implements Parcelable {
/**
* Returns the current color of the hint text.
+ *
+ * @see TextView#getCurrentHintTextColor()
*/
- public @ColorInt int getTextColorHint() {
- return mTextColorHint;
+ public @ColorInt int getHintTextColor() {
+ return mHintTextColor;
}
/**
- * Returns the text color for links.
+ * Returns the text color used to paint the links in the editor.
+ *
+ * @see TextView#getLinkTextColors()
*/
- @Nullable
- public ColorStateList getTextColorLink() {
- return mTextColorLink;
+ public @ColorInt int getLinkTextColor() {
+ return mLinkTextColor;
}
- /**
- * Returns the max length of text, which is used to set an input filter to constrain the text
- * length to the specified number. Returns -1 when there is no {@link InputFilter.LengthFilter}
- * in the Editor.
- */
- public int getMaxLength() {
- return mMaxLength;
- }
@Override
public boolean equals(Object o) {
@@ -456,27 +463,29 @@ public final class TextAppearanceInfo implements Parcelable {
&& mTextFontWeight == that.mTextFontWeight && mTextStyle == that.mTextStyle
&& mAllCaps == that.mAllCaps && Float.compare(that.mShadowDx, mShadowDx) == 0
&& Float.compare(that.mShadowDy, mShadowDy) == 0 && Float.compare(
- that.mShadowRadius, mShadowRadius) == 0 && mMaxLength == that.mMaxLength
+ that.mShadowRadius, mShadowRadius) == 0 && that.mShadowColor == mShadowColor
&& mElegantTextHeight == that.mElegantTextHeight
&& mFallbackLineSpacing == that.mFallbackLineSpacing && Float.compare(
that.mLetterSpacing, mLetterSpacing) == 0 && mLineBreakStyle == that.mLineBreakStyle
&& mLineBreakWordStyle == that.mLineBreakWordStyle
- && mTextColorHighlight == that.mTextColorHighlight && mTextColor == that.mTextColor
- && mTextColorLink.getDefaultColor() == that.mTextColorLink.getDefaultColor()
- && mTextColorHint == that.mTextColorHint && Objects.equals(
- mTextLocales, that.mTextLocales) && Objects.equals(mSystemFontFamilyName,
- that.mSystemFontFamilyName) && Objects.equals(mFontFeatureSettings,
- that.mFontFeatureSettings) && Objects.equals(mFontVariationSettings,
- that.mFontVariationSettings) && Float.compare(that.mTextScaleX, mTextScaleX) == 0;
+ && mHighlightTextColor == that.mHighlightTextColor
+ && mTextColor == that.mTextColor
+ && mLinkTextColor == that.mLinkTextColor
+ && mHintTextColor == that.mHintTextColor
+ && Objects.equals(mTextLocales, that.mTextLocales)
+ && Objects.equals(mSystemFontFamilyName, that.mSystemFontFamilyName)
+ && Objects.equals(mFontFeatureSettings, that.mFontFeatureSettings)
+ && Objects.equals(mFontVariationSettings, that.mFontVariationSettings)
+ && Float.compare(that.mTextScaleX, mTextScaleX) == 0;
}
@Override
public int hashCode() {
return Objects.hash(mTextSize, mTextLocales, mSystemFontFamilyName, mTextFontWeight,
- mTextStyle, mAllCaps, mShadowDx, mShadowDy, mShadowRadius, mElegantTextHeight,
- mFallbackLineSpacing, mLetterSpacing, mFontFeatureSettings, mFontVariationSettings,
- mLineBreakStyle, mLineBreakWordStyle, mTextScaleX, mTextColorHighlight, mTextColor,
- mTextColorHint, mTextColorLink, mMaxLength);
+ mTextStyle, mAllCaps, mShadowDx, mShadowDy, mShadowRadius, mShadowColor,
+ mElegantTextHeight, mFallbackLineSpacing, mLetterSpacing, mFontFeatureSettings,
+ mFontVariationSettings, mLineBreakStyle, mLineBreakWordStyle, mTextScaleX,
+ mHighlightTextColor, mTextColor, mHintTextColor, mLinkTextColor);
}
@Override
@@ -491,6 +500,7 @@ public final class TextAppearanceInfo implements Parcelable {
+ ", mShadowDx=" + mShadowDx
+ ", mShadowDy=" + mShadowDy
+ ", mShadowRadius=" + mShadowRadius
+ + ", mShadowColor=" + mShadowColor
+ ", mElegantTextHeight=" + mElegantTextHeight
+ ", mFallbackLineSpacing=" + mFallbackLineSpacing
+ ", mLetterSpacing=" + mLetterSpacing
@@ -499,11 +509,290 @@ public final class TextAppearanceInfo implements Parcelable {
+ ", mLineBreakStyle=" + mLineBreakStyle
+ ", mLineBreakWordStyle=" + mLineBreakWordStyle
+ ", mTextScaleX=" + mTextScaleX
- + ", mTextColorHighlight=" + mTextColorHighlight
+ + ", mHighlightTextColor=" + mHighlightTextColor
+ ", mTextColor=" + mTextColor
- + ", mTextColorHint=" + mTextColorHint
- + ", mTextColorLink=" + mTextColorLink
- + ", mMaxLength=" + mMaxLength
+ + ", mHintTextColor=" + mHintTextColor
+ + ", mLinkTextColor=" + mLinkTextColor
+ '}';
}
+
+ /**
+ * Builder for {@link TextAppearanceInfo}.
+ */
+ public static final class Builder {
+ private @Px float mTextSize = -1;
+ private @NonNull LocaleList mTextLocales = LocaleList.getAdjustedDefault();
+ @Nullable private String mSystemFontFamilyName = null;
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+ private int mTextFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
+ private @Typeface.Style int mTextStyle = NORMAL;
+ private boolean mAllCaps = false;
+ private @Px float mShadowDx = 0;
+ private @Px float mShadowDy = 0;
+ private @Px float mShadowRadius = 0;
+ private @ColorInt int mShadowColor = 0;
+ private boolean mElegantTextHeight = false;
+ private boolean mFallbackLineSpacing = false;
+ private float mLetterSpacing = 0;
+ @Nullable private String mFontFeatureSettings = null;
+ @Nullable private String mFontVariationSettings = null;
+ @LineBreakConfig.LineBreakStyle
+ private int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE;
+ @LineBreakConfig.LineBreakWordStyle
+ private int mLineBreakWordStyle = LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+ private float mTextScaleX = 1;
+ private @ColorInt int mHighlightTextColor = 0;
+ private @ColorInt int mTextColor = 0;
+ private @ColorInt int mHintTextColor = 0;
+ private @ColorInt int mLinkTextColor = 0;
+
+ /**
+ * Set the text size (in pixels) obtained from the current editor.
+ */
+ @NonNull
+ public Builder setTextSize(@Px float textSize) {
+ mTextSize = textSize;
+ return this;
+ }
+
+ /**
+ * Set the {@link LocaleList} of the text.
+ */
+ @NonNull
+ public Builder setTextLocales(@NonNull LocaleList textLocales) {
+ mTextLocales = textLocales;
+ return this;
+ }
+
+ /**
+ * Set the system font family name if the {@link Typeface} of the text is created from a
+ * system font family.
+ *
+ * @see Typeface#getSystemFontFamilyName()
+ */
+ @NonNull
+ public Builder setSystemFontFamilyName(@Nullable String systemFontFamilyName) {
+ mSystemFontFamilyName = systemFontFamilyName;
+ return this;
+ }
+
+ /**
+ * Set the weight of the text.
+ */
+ @NonNull
+ public Builder setTextFontWeight(
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED,
+ to = FontStyle.FONT_WEIGHT_MAX) int textFontWeight) {
+ mTextFontWeight = textFontWeight;
+ return this;
+ }
+
+ /**
+ * Set the style (normal, bold, italic, bold|italic) of the text.
+ *
+ * @see Typeface
+ */
+ @NonNull
+ public Builder setTextStyle(@Typeface.Style int textStyle) {
+ mTextStyle = textStyle;
+ return this;
+ }
+
+ /**
+ * Set whether the transformation method applied to the current editor is set to all caps.
+ *
+ * @see TextView#setAllCaps(boolean)
+ * @see TextView#setTransformationMethod(TransformationMethod)
+ */
+ @NonNull
+ public Builder setAllCaps(boolean allCaps) {
+ mAllCaps = allCaps;
+ return this;
+ }
+
+ /**
+ * Set the horizontal offset (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ @NonNull
+ public Builder setShadowDx(@Px float shadowDx) {
+ mShadowDx = shadowDx;
+ return this;
+ }
+
+ /**
+ * Set the vertical offset (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ @NonNull
+ public Builder setShadowDy(@Px float shadowDy) {
+ mShadowDy = shadowDy;
+ return this;
+ }
+
+ /**
+ * Set the blur radius (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ @NonNull
+ public Builder setShadowRadius(@Px float shadowRadius) {
+ mShadowRadius = shadowRadius;
+ return this;
+ }
+
+ /**
+ * Set the color of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ @NonNull
+ public Builder setShadowColor(@ColorInt int shadowColor) {
+ mShadowColor = shadowColor;
+ return this;
+ }
+
+ /**
+ * Set the elegant height metrics flag. This setting selects font variants that
+ * have not been compacted to fit Latin-based vertical metrics, and also increases
+ * top and bottom bounds to provide more space.
+ *
+ * @see Paint#isElegantTextHeight()
+ */
+ @NonNull
+ public Builder setElegantTextHeight(boolean elegantTextHeight) {
+ mElegantTextHeight = elegantTextHeight;
+ return this;
+ }
+
+ /**
+ * Set whether to expand linespacing based on fallback fonts.
+ *
+ * @see TextView#setFallbackLineSpacing(boolean)
+ */
+ @NonNull
+ public Builder setFallbackLineSpacing(boolean fallbackLineSpacing) {
+ mFallbackLineSpacing = fallbackLineSpacing;
+ return this;
+ }
+
+ /**
+ * Set the text letter-spacing, which determines the spacing between characters.
+ * The value is in 'EM' units. Normally, this value is 0.0.
+ */
+ @NonNull
+ public Builder setLetterSpacing(float letterSpacing) {
+ mLetterSpacing = letterSpacing;
+ return this;
+ }
+
+ /**
+ * Set the font feature settings.
+ *
+ * @see Paint#getFontFeatureSettings()
+ */
+ @NonNull
+ public Builder setFontFeatureSettings(@Nullable String fontFeatureSettings) {
+ mFontFeatureSettings = fontFeatureSettings;
+ return this;
+ }
+
+ /**
+ * Set the font variation settings. Returns null if no variation is specified.
+ *
+ * @see Paint#getFontVariationSettings()
+ */
+ @NonNull
+ public Builder setFontVariationSettings(@Nullable String fontVariationSettings) {
+ mFontVariationSettings = fontVariationSettings;
+ return this;
+ }
+
+ /**
+ * Set the line-break strategies for text wrapping.
+ *
+ * @see TextView#setLineBreakStyle(int)
+ */
+ @NonNull
+ public Builder setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
+ mLineBreakStyle = lineBreakStyle;
+ return this;
+ }
+
+ /**
+ * Set the line-break word strategies for text wrapping.
+ *
+ * @see TextView#setLineBreakWordStyle(int)
+ */
+ @NonNull
+ public Builder setLineBreakWordStyle(
+ @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
+ mLineBreakWordStyle = lineBreakWordStyle;
+ return this;
+ }
+
+ /**
+ * Set the extent by which text should be stretched horizontally.
+ */
+ @NonNull
+ public Builder setTextScaleX(float textScaleX) {
+ mTextScaleX = textScaleX;
+ return this;
+ }
+
+ /**
+ * Set the color of the text selection highlight.
+ *
+ * @see TextView#getHighlightColor()
+ */
+ @NonNull
+ public Builder setHighlightTextColor(@ColorInt int highlightTextColor) {
+ mHighlightTextColor = highlightTextColor;
+ return this;
+ }
+
+ /**
+ * Set the current text color of the editor.
+ *
+ * @see TextView#getCurrentTextColor()
+ */
+ @NonNull
+ public Builder setTextColor(@ColorInt int textColor) {
+ mTextColor = textColor;
+ return this;
+ }
+
+ /**
+ * Set the current color of the hint text.
+ *
+ * @see TextView#getCurrentHintTextColor()
+ */
+ @NonNull
+ public Builder setHintTextColor(@ColorInt int hintTextColor) {
+ mHintTextColor = hintTextColor;
+ return this;
+ }
+
+ /**
+ * Set the text color used to paint the links in the editor.
+ *
+ * @see TextView#getLinkTextColors()
+ */
+ @NonNull
+ public Builder setLinkTextColor(@ColorInt int linkTextColor) {
+ mLinkTextColor = linkTextColor;
+ return this;
+ }
+
+ /**
+ * Returns {@link TextAppearanceInfo} using parameters in this
+ * {@link TextAppearanceInfo.Builder}.
+ */
+ @NonNull
+ public TextAppearanceInfo build() {
+ return new TextAppearanceInfo(this);
+ }
+ }
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 56524a2c01ef..5740f86b3486 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -21,6 +21,7 @@ import static android.widget.TextView.ACCESSIBILITY_ACTION_SMART_START_ID;
import android.R;
import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -37,6 +38,7 @@ import android.content.UndoOperation;
import android.content.UndoOwner;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -49,8 +51,10 @@ import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.RenderNode;
+import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.fonts.FontStyle;
import android.os.Build;
import android.os.Bundle;
import android.os.LocaleList;
@@ -4762,8 +4766,41 @@ public class Editor {
}
if (includeTextAppearance) {
- TextAppearanceInfo textAppearanceInfo = new TextAppearanceInfo(mTextView);
- builder.setTextAppearanceInfo(textAppearanceInfo);
+ Typeface typeface = mTextView.getPaint().getTypeface();
+ String systemFontFamilyName = null;
+ int textFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
+ if (typeface != null) {
+ systemFontFamilyName = typeface.getSystemFontFamilyName();
+ textFontWeight = typeface.getWeight();
+ }
+ ColorStateList linkTextColors = mTextView.getLinkTextColors();
+ @ColorInt int linkTextColor = linkTextColors != null
+ ? linkTextColors.getDefaultColor() : 0;
+
+ TextAppearanceInfo.Builder appearanceBuilder = new TextAppearanceInfo.Builder();
+ appearanceBuilder.setTextSize(mTextView.getTextSize())
+ .setTextLocales(mTextView.getTextLocales())
+ .setSystemFontFamilyName(systemFontFamilyName)
+ .setTextFontWeight(textFontWeight)
+ .setTextStyle(mTextView.getTypefaceStyle())
+ .setAllCaps(mTextView.isAllCaps())
+ .setShadowDx(mTextView.getShadowDx())
+ .setShadowDy(mTextView.getShadowDy())
+ .setShadowRadius(mTextView.getShadowRadius())
+ .setShadowColor(mTextView.getShadowColor())
+ .setElegantTextHeight(mTextView.isElegantTextHeight())
+ .setFallbackLineSpacing(mTextView.isFallbackLineSpacing())
+ .setLetterSpacing(mTextView.getLetterSpacing())
+ .setFontFeatureSettings(mTextView.getFontFeatureSettings())
+ .setFontVariationSettings(mTextView.getFontVariationSettings())
+ .setLineBreakStyle(mTextView.getLineBreakStyle())
+ .setLineBreakWordStyle(mTextView.getLineBreakWordStyle())
+ .setTextScaleX(mTextView.getTextScaleX())
+ .setHighlightTextColor(mTextView.getHighlightColor())
+ .setTextColor(mTextView.getCurrentTextColor())
+ .setHintTextColor(mTextView.getCurrentHintTextColor())
+ .setLinkTextColor(linkTextColor);
+ builder.setTextAppearanceInfo(appearanceBuilder.build());
}
imm.updateCursorAnchorInfo(mTextView, builder.build());
diff --git a/core/java/android/window/IBackAnimationFinishedCallback.aidl b/core/java/android/window/IBackAnimationFinishedCallback.aidl
index 8afc003256ed..f034339add6e 100644
--- a/core/java/android/window/IBackAnimationFinishedCallback.aidl
+++ b/core/java/android/window/IBackAnimationFinishedCallback.aidl
@@ -22,6 +22,6 @@ package android.window;
* @param trigger Whether the back gesture has passed the triggering threshold.
* {@hide}
*/
-oneway interface IBackAnimationFinishedCallback {
+interface IBackAnimationFinishedCallback {
void onAnimationFinished(in boolean triggerBack);
} \ No newline at end of file
diff --git a/core/java/android/window/WindowProvider.java b/core/java/android/window/WindowProvider.java
index b078b9362b90..dbdc68fbfd44 100644
--- a/core/java/android/window/WindowProvider.java
+++ b/core/java/android/window/WindowProvider.java
@@ -15,8 +15,10 @@
*/
package android.window;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
+import android.os.IBinder;
import android.view.WindowManager.LayoutParams.WindowType;
/**
@@ -36,4 +38,11 @@ public interface WindowProvider {
/** Gets the launch options of this provider */
@Nullable
Bundle getWindowContextOptions();
+
+ /**
+ * Gets the WindowContextToken of this provider.
+ * @see android.content.Context#getWindowContextToken
+ */
+ @NonNull
+ IBinder getWindowContextToken();
}
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index 2d2c8de72646..fdc3e5af8d8b 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -27,7 +27,10 @@ import android.annotation.UiContext;
import android.app.ActivityThread;
import android.app.LoadedApk;
import android.app.Service;
+import android.content.ComponentCallbacks;
+import android.content.ComponentCallbacksController;
import android.content.Context;
+import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.IBinder;
@@ -54,6 +57,8 @@ public abstract class WindowProviderService extends Service implements WindowPro
private final WindowContextController mController = new WindowContextController(mWindowToken);
private WindowManager mWindowManager;
private boolean mInitialized;
+ private final ComponentCallbacksController mCallbacksController =
+ new ComponentCallbacksController();
/**
* Returns {@code true} if the {@code windowContextOptions} declares that it is a
@@ -118,6 +123,48 @@ public abstract class WindowProviderService extends Service implements WindowPro
return mOptions;
}
+ @SuppressLint({"OnNameExpected", "ExecutorRegistration"})
+ // Suppress lint because this is a legacy named function and doesn't have an optional param
+ // for executor.
+ // TODO(b/259347943): Update documentation for U.
+ /**
+ * Here we override to prevent WindowProviderService from invoking
+ * {@link Application.registerComponentCallback}, which will result in callback registered
+ * for process-level Configuration change updates.
+ */
+ @Override
+ public void registerComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ // For broadcasting Configuration Changes.
+ mCallbacksController.registerCallbacks(callback);
+ }
+
+ @SuppressLint("OnNameExpected")
+ @Override
+ public void unregisterComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ mCallbacksController.unregisterCallbacks(callback);
+ }
+
+ @SuppressLint("OnNameExpected")
+ @Override
+ public void onConfigurationChanged(@Nullable Configuration configuration) {
+ // This is only called from WindowTokenClient.
+ mCallbacksController.dispatchConfigurationChanged(configuration);
+ }
+
+ /**
+ * Override {@link Service}'s empty implementation and listen to {@link ActivityThread} for
+ * low memory and trim memory events.
+ */
+ @Override
+ public void onLowMemory() {
+ mCallbacksController.dispatchLowMemory();
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ mCallbacksController.dispatchTrimMemory(level);
+ }
+
/**
* Returns the display ID to launch this {@link WindowProviderService}.
*
@@ -181,5 +228,6 @@ public abstract class WindowProviderService extends Service implements WindowPro
public void onDestroy() {
super.onDestroy();
mController.detachIfNeeded();
+ mCallbacksController.clearCallbacks();
}
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 09f099120812..614f96255acb 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -91,7 +91,6 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.UiThread;
import android.annotation.WorkerThread;
-import android.app.ActivityThread;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
@@ -417,7 +416,6 @@ public class InteractionJankMonitor {
public InteractionJankMonitor(@NonNull HandlerThread worker) {
// Check permission early.
DeviceConfig.enforceReadPermission(
- ActivityThread.currentApplication().getApplicationContext(),
DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR);
mRunningTrackers = new SparseArray<>();
diff --git a/core/proto/android/app/location_time_zone_manager.proto b/core/proto/android/app/location_time_zone_manager.proto
index 5fdcfdf35a37..7037a6c4f68a 100644
--- a/core/proto/android/app/location_time_zone_manager.proto
+++ b/core/proto/android/app/location_time_zone_manager.proto
@@ -40,7 +40,7 @@ enum ControllerStateEnum {
message LocationTimeZoneManagerServiceStateProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
- optional GeolocationTimeZoneSuggestionProto last_suggestion = 1;
+ optional LocationTimeZoneProviderEventProto last_event = 1;
repeated TimeZoneProviderStateProto primary_provider_states = 2;
repeated TimeZoneProviderStateProto secondary_provider_states = 3;
repeated ControllerStateEnum controller_states = 4;
diff --git a/core/proto/android/app/time_zone_detector.proto b/core/proto/android/app/time_zone_detector.proto
index b52aa828bef9..cd4a36fafef0 100644
--- a/core/proto/android/app/time_zone_detector.proto
+++ b/core/proto/android/app/time_zone_detector.proto
@@ -22,13 +22,38 @@ import "frameworks/base/core/proto/android/privacy.proto";
option java_multiple_files = true;
option java_outer_classname = "TimeZoneDetectorProto";
-// Represents a GeolocationTimeZoneSuggestion that can be / has been passed to the time zone
+// Represents a LocationTimeZoneProviderEvent that can be / has been passed to the time zone
// detector.
+message LocationTimeZoneProviderEventProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional GeolocationTimeZoneSuggestionProto suggestion = 1;
+ repeated string debug_info = 2;
+ optional LocationTimeZoneAlgorithmStatusProto algorithm_status = 3;
+}
+
+// Represents a LocationTimeZoneAlgorithmStatus that can be / has been passed to the time zone
+// detector.
+message LocationTimeZoneAlgorithmStatusProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional DetectionAlgorithmStatusEnum status = 1;
+}
+
+// The state enum for detection algorithms.
+enum DetectionAlgorithmStatusEnum {
+ DETECTION_ALGORITHM_STATUS_UNKNOWN = 0;
+ DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED = 1;
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING = 2;
+ DETECTION_ALGORITHM_STATUS_RUNNING = 3;
+}
+
+// Represents a GeolocationTimeZoneSuggestion that can be contained in a
+// LocationTimeZoneProviderEvent.
message GeolocationTimeZoneSuggestionProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
repeated string zone_ids = 1;
- repeated string debug_info = 2;
}
/*
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 196ea59fe0f4..e24d667e8600 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3177,10 +3177,10 @@
<permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"
android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role"/>
- <!-- Allows an application to hint that a broadcast is associated with an
- "interactive" usage scenario
+ <!-- Allows an application to hint that a component lifecycle operation such as sending
+ a broadcast is associated with an "interactive" usage scenario.
@hide -->
- <permission android:name="android.permission.BROADCAST_OPTION_INTERACTIVE"
+ <permission android:name="android.permission.COMPONENT_OPTION_INTERACTIVE"
android:protectionLevel="signature|privileged" />
<!-- @SystemApi Must be required by activities that handle the intent action
diff --git a/core/tests/coretests/src/android/app/activity/BroadcastTest.java b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
index 7e875ada7267..10452fd64430 100644
--- a/core/tests/coretests/src/android/app/activity/BroadcastTest.java
+++ b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
@@ -541,35 +541,35 @@ public class BroadcastTest extends ActivityTestsBase {
public void testBroadcastOption_interactive() throws Exception {
final BroadcastOptions options = BroadcastOptions.makeBasic();
- options.setInteractiveBroadcast(true);
+ options.setInteractive(true);
final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);
try {
getContext().sendBroadcast(intent, null, options.toBundle());
- fail("No exception thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+ fail("No exception thrown with BroadcastOptions.setInteractive(true)");
} catch (SecurityException se) {
// Expected, correct behavior - this case intentionally empty
} catch (Exception e) {
fail("Unexpected exception " + e.getMessage()
- + " thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+ + " thrown with BroadcastOptions.setInteractive(true)");
}
}
public void testBroadcastOption_interactive_PendingIntent() throws Exception {
final BroadcastOptions options = BroadcastOptions.makeBasic();
- options.setInteractiveBroadcast(true);
+ options.setInteractive(true);
final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);
PendingIntent brPending = PendingIntent.getBroadcast(getContext(),
1, intent, PendingIntent.FLAG_IMMUTABLE);
try {
brPending.send(getContext(), 1, null, null, null, null, options.toBundle());
- fail("No exception thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+ fail("No exception thrown with BroadcastOptions.setInteractive(true)");
} catch (SecurityException se) {
// Expected, correct behavior - this case intentionally empty
} catch (Exception e) {
fail("Unexpected exception " + e.getMessage()
- + " thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+ + " thrown with BroadcastOptions.setInteractive(true)");
} finally {
brPending.cancel();
}
diff --git a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
index 37cf4700c1d0..4d5b0d2d4ae7 100644
--- a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
@@ -46,6 +46,7 @@ import java.util.Set;
public class BackupAgentTest {
// An arbitrary user.
private static final UserHandle USER_HANDLE = new UserHandle(15);
+ private static final String DATA_TYPE_BACKED_UP = "test data type";
@Mock FullBackup.BackupScheme mBackupScheme;
@@ -73,6 +74,42 @@ public class BackupAgentTest {
assertThat(rules).isEqualTo(expectedRules);
}
+ @Test
+ public void getBackupRestoreEventLogger_beforeOnCreate_isNull() {
+ BackupAgent agent = new TestFullBackupAgent();
+
+ assertThat(agent.getBackupRestoreEventLogger()).isNull();
+ }
+
+ @Test
+ public void getBackupRestoreEventLogger_afterOnCreateForBackup_initializedForBackup() {
+ BackupAgent agent = new TestFullBackupAgent();
+ agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+
+ assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(1);
+ }
+
+ @Test
+ public void getBackupRestoreEventLogger_afterOnCreateForRestore_initializedForRestore() {
+ BackupAgent agent = new TestFullBackupAgent();
+ agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+
+ assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(1);
+ }
+
+ @Test
+ public void getBackupRestoreEventLogger_afterBackup_containsLogsLoggedByAgent()
+ throws Exception {
+ BackupAgent agent = new TestFullBackupAgent();
+ agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+
+ // TestFullBackupAgent logs DATA_TYPE_BACKED_UP when onFullBackup is called.
+ agent.onFullBackup(new FullBackupDataOutput(/* quota = */ 0));
+
+ assertThat(agent.getBackupRestoreEventLogger().getLoggingResults().get(0).getDataType())
+ .isEqualTo(DATA_TYPE_BACKED_UP);
+ }
+
private BackupAgent getAgentForOperationType(@OperationType int operationType) {
BackupAgent agent = new TestFullBackupAgent();
agent.onCreate(USER_HANDLE, operationType);
@@ -88,6 +125,11 @@ public class BackupAgentTest {
}
@Override
+ public void onFullBackup(FullBackupDataOutput data) {
+ getBackupRestoreEventLogger().logItemsBackedUp(DATA_TYPE_BACKED_UP, 1);
+ }
+
+ @Override
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
// Left empty as this is a full backup agent.
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index bbd2ef38d786..b9fdc6d2aa23 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -25,6 +25,7 @@ import static junit.framework.Assert.fail;
import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
+import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
@@ -35,6 +36,7 @@ import org.junit.runner.RunWith;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@@ -236,10 +238,10 @@ public class BackupRestoreEventLoggerTest {
mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, ERROR_1);
mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, ERROR_2);
- int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
- .getErrors().get(ERROR_1);
- int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
- .getErrors().get(ERROR_2);
+ int firstErrorTypeCount =
+ getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_1);
+ int secondErrorTypeCount =
+ getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_2);
assertThat(firstErrorTypeCount).isEqualTo(firstCount);
assertThat(secondErrorTypeCount).isEqualTo(secondCount);
}
@@ -253,16 +255,54 @@ public class BackupRestoreEventLoggerTest {
mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, ERROR_1);
mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, ERROR_2);
- int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
- .getErrors().get(ERROR_1);
- int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
- .getErrors().get(ERROR_2);
+ int firstErrorTypeCount =
+ getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_1);
+ int secondErrorTypeCount =
+ getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_2);
assertThat(firstErrorTypeCount).isEqualTo(firstCount);
assertThat(secondErrorTypeCount).isEqualTo(secondCount);
}
- private static DataTypeResult getResultForDataType(BackupRestoreEventLogger logger,
- @BackupRestoreDataType String dataType) {
+ @Test
+ public void testGetLoggingResults_resultsParceledAndUnparceled_recreatedCorrectly() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+ int firstTypeSuccessCount = 1;
+ int firstTypeErrorOneCount = 2;
+ int firstTypeErrorTwoCount = 3;
+ mLogger.logItemsRestored(DATA_TYPE_1, firstTypeSuccessCount);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstTypeErrorOneCount, ERROR_1);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstTypeErrorTwoCount, ERROR_2);
+ mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_1);
+ int secondTypeSuccessCount = 4;
+ int secondTypeErrorOneCount = 5;
+ mLogger.logItemsRestored(DATA_TYPE_2, secondTypeSuccessCount);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_2, secondTypeErrorOneCount, ERROR_1);
+
+ List<DataTypeResult> resultsList = mLogger.getLoggingResults();
+ Parcel parcel = Parcel.obtain();
+
+ parcel.writeParcelableList(resultsList, /* flags= */ 0);
+
+ parcel.setDataPosition(0);
+ List<DataTypeResult> recreatedList = new ArrayList<>();
+ parcel.readParcelableList(
+ recreatedList, DataTypeResult.class.getClassLoader(), DataTypeResult.class);
+
+ assertThat(recreatedList.get(0).getDataType()).isEqualTo(DATA_TYPE_1);
+ assertThat(recreatedList.get(0).getSuccessCount()).isEqualTo(firstTypeSuccessCount);
+ assertThat(recreatedList.get(0).getFailCount())
+ .isEqualTo(firstTypeErrorOneCount + firstTypeErrorTwoCount);
+ assertThat(recreatedList.get(0).getErrors().get(ERROR_1)).isEqualTo(firstTypeErrorOneCount);
+ assertThat(recreatedList.get(0).getErrors().get(ERROR_2)).isEqualTo(firstTypeErrorTwoCount);
+ assertThat(recreatedList.get(1).getDataType()).isEqualTo(DATA_TYPE_2);
+ assertThat(recreatedList.get(1).getSuccessCount()).isEqualTo(secondTypeSuccessCount);
+ assertThat(recreatedList.get(1).getFailCount()).isEqualTo(secondTypeErrorOneCount);
+ assertThat(recreatedList.get(1).getErrors().get(ERROR_1))
+ .isEqualTo(secondTypeErrorOneCount);
+ }
+
+ private static DataTypeResult getResultForDataType(
+ BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
Optional<DataTypeResult> result = getResultForDataTypeIfPresent(logger, dataType);
if (result.isEmpty()) {
fail("Failed to find result for data type: " + dataType);
@@ -273,8 +313,9 @@ public class BackupRestoreEventLoggerTest {
private static Optional<DataTypeResult> getResultForDataTypeIfPresent(
BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
List<DataTypeResult> resultList = logger.getLoggingResults();
- return resultList.stream().filter(
- dataTypeResult -> dataTypeResult.getDataType().equals(dataType)).findAny();
+ return resultList.stream()
+ .filter(dataTypeResult -> dataTypeResult.getDataType().equals(dataType))
+ .findAny();
}
private byte[] getMetaDataHash(String metaData) {
diff --git a/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java b/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java
new file mode 100644
index 000000000000..f57ee43b76c8
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_UNKNOWN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.app.time.DetectorStatusTypes.DetectorStatus;
+
+import org.junit.Test;
+
+public class DetectorStatusTypesTest {
+
+ @Test
+ public void testRequireValidDetectionAlgorithmStatus() {
+ for (@DetectionAlgorithmStatus int status = DETECTION_ALGORITHM_STATUS_UNKNOWN;
+ status <= DETECTION_ALGORITHM_STATUS_RUNNING; status++) {
+ assertEquals(status, DetectorStatusTypes.requireValidDetectionAlgorithmStatus(status));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.requireValidDetectionAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_UNKNOWN - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.requireValidDetectionAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING + 1));
+ }
+
+ @Test
+ public void testFormatAndParseDetectionAlgorithmStatus() {
+ for (@DetectionAlgorithmStatus int status = DETECTION_ALGORITHM_STATUS_UNKNOWN;
+ status <= DETECTION_ALGORITHM_STATUS_RUNNING; status++) {
+ assertEquals(status, DetectorStatusTypes.detectionAlgorithmStatusFromString(
+ DetectorStatusTypes.detectionAlgorithmStatusToString(status)));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusToString(
+ DETECTION_ALGORITHM_STATUS_UNKNOWN - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusToString(
+ DETECTION_ALGORITHM_STATUS_RUNNING + 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString(null));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString(""));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString("FOO"));
+ }
+
+ @Test
+ public void testRequireValidDetectorStatus() {
+ for (@DetectorStatus int status = DETECTOR_STATUS_UNKNOWN;
+ status <= DETECTOR_STATUS_RUNNING; status++) {
+ assertEquals(status, DetectorStatusTypes.requireValidDetectorStatus(status));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.requireValidDetectorStatus(DETECTOR_STATUS_UNKNOWN - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.requireValidDetectorStatus(DETECTOR_STATUS_RUNNING + 1));
+ }
+
+ @Test
+ public void testFormatAndParseDetectorStatus() {
+ for (@DetectorStatus int status = DETECTOR_STATUS_UNKNOWN;
+ status <= DETECTOR_STATUS_RUNNING; status++) {
+ assertEquals(status, DetectorStatusTypes.detectorStatusFromString(
+ DetectorStatusTypes.detectorStatusToString(status)));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusToString(DETECTOR_STATUS_UNKNOWN - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusToString(DETECTOR_STATUS_RUNNING + 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString(null));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString(""));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString("FOO"));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java
new file mode 100644
index 000000000000..a648a885aea2
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus;
+import android.service.timezone.TimeZoneProviderStatus;
+
+import org.junit.Test;
+
+public class LocationTimeZoneAlgorithmStatusTest {
+
+ private static final TimeZoneProviderStatus ARBITRARY_PROVIDER_RUNNING_STATUS =
+ new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+ .build();
+
+ @Test
+ public void testConstructorValidation() {
+ // Sample some invalid cases
+
+ // There can't be a reported provider status if the algorithm isn't running.
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS));
+
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null));
+
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS));
+
+ // No reported provider status expected if the associated provider isn't ready / present.
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null));
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_READY, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_READY, null,
+ PROVIDER_STATUS_NOT_PRESENT, ARBITRARY_PROVIDER_RUNNING_STATUS));
+ }
+
+ @Test
+ public void testEquals() {
+ LocationTimeZoneAlgorithmStatus one = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertEqualsAndHashCode(one, one);
+
+ {
+ LocationTimeZoneAlgorithmStatus two = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertEqualsAndHashCode(one, two);
+ }
+
+ {
+ LocationTimeZoneAlgorithmStatus three = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_NOT_READY, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+ }
+
+ @Test
+ public void testParcelable() {
+ // Primary provider only.
+ {
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+
+ // Secondary provider only
+ {
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+
+ // Algorithm not running.
+ {
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+ }
+
+ @Test
+ public void testRequireValidProviderStatus() {
+ for (@ProviderStatus int status = PROVIDER_STATUS_NOT_PRESENT;
+ status <= PROVIDER_STATUS_IS_UNCERTAIN; status++) {
+ assertEquals(status,
+ LocationTimeZoneAlgorithmStatus.requireValidProviderStatus(status));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.requireValidProviderStatus(
+ PROVIDER_STATUS_NOT_PRESENT - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.requireValidProviderStatus(
+ PROVIDER_STATUS_IS_UNCERTAIN + 1));
+ }
+
+ @Test
+ public void testFormatAndParseProviderStatus() {
+ for (@ProviderStatus int status = PROVIDER_STATUS_NOT_PRESENT;
+ status <= PROVIDER_STATUS_IS_UNCERTAIN; status++) {
+ assertEquals(status, LocationTimeZoneAlgorithmStatus.providerStatusFromString(
+ LocationTimeZoneAlgorithmStatus.providerStatusToString(status)));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusToString(
+ PROVIDER_STATUS_NOT_PRESENT - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusToString(
+ PROVIDER_STATUS_IS_UNCERTAIN + 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString(null));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString(""));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString("FOO"));
+ }
+
+ @Test
+ public void testParseCommandlineArg_noNullReportedStatuses() {
+ LocationTimeZoneAlgorithmStatus status = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+ assertEquals(status,
+ LocationTimeZoneAlgorithmStatus.parseCommandlineArg(status.toString()));
+ }
+
+ @Test
+ public void testParseCommandlineArg_withNullReportedStatuses() {
+ LocationTimeZoneAlgorithmStatus status = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, null,
+ PROVIDER_STATUS_IS_UNCERTAIN, null);
+ assertEquals(status,
+ LocationTimeZoneAlgorithmStatus.parseCommandlineArg(status.toString()));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java b/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java
new file mode 100644
index 000000000000..b90c485bbbb6
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+
+import static org.junit.Assert.assertNotEquals;
+
+import org.junit.Test;
+
+public class TelephonyTimeZoneAlgorithmStatusTest {
+
+ @Test
+ public void testEquals() {
+ TelephonyTimeZoneAlgorithmStatus one = new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
+ assertEqualsAndHashCode(one, one);
+
+ {
+ TelephonyTimeZoneAlgorithmStatus two = new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
+ assertEqualsAndHashCode(one, two);
+ }
+
+ {
+ TelephonyTimeZoneAlgorithmStatus three = new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+ }
+
+ @Test
+ public void testParcelable() {
+ // Algorithm running.
+ {
+ TelephonyTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+
+ // Algorithm not running.
+ {
+ TelephonyTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java b/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java
new file mode 100644
index 000000000000..dfff7ecdf989
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 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 android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+
+import static org.junit.Assert.assertNotEquals;
+
+import org.junit.Test;
+
+public class TimeZoneDetectorStatusTest {
+
+ private static final TelephonyTimeZoneAlgorithmStatus ARBITRARY_TELEPHONY_ALGORITHM_STATUS =
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING);
+
+ private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_ALGORITHM_STATUS =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null,
+ LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT, null);
+
+ @Test
+ public void testEquals() {
+ TimeZoneDetectorStatus one = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertEqualsAndHashCode(one, one);
+
+ {
+ TimeZoneDetectorStatus two = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertEqualsAndHashCode(one, two);
+ }
+
+ {
+ TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+
+ {
+ TelephonyTimeZoneAlgorithmStatus telephonyAlgorithmStatus =
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
+ assertNotEquals(telephonyAlgorithmStatus, ARBITRARY_TELEPHONY_ALGORITHM_STATUS);
+
+ TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+ telephonyAlgorithmStatus, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+
+ {
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null,
+ LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null);
+ assertNotEquals(locationAlgorithmStatus, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+
+ TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS, locationAlgorithmStatus);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+ }
+
+ @Test
+ public void testParcelable() {
+ // Detector running.
+ {
+ TimeZoneDetectorStatus locationAlgorithmStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING, ARBITRARY_TELEPHONY_ALGORITHM_STATUS,
+ ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+
+ // Detector not running.
+ {
+ TimeZoneDetectorStatus locationAlgorithmStatus =
+ new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS,
+ ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index b516e1407b11..2192b5ca219c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -36,6 +36,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
import android.window.WindowContext;
+import android.window.WindowProvider;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -71,7 +72,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>();
- private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners =
+ private final Map<IBinder, ConfigurationChangeListener> mConfigurationChangeListeners =
new ArrayMap<>();
public WindowLayoutComponentImpl(@NonNull Context context) {
@@ -121,21 +122,21 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
if (!context.isUiContext()) {
throw new IllegalArgumentException("Context must be a UI Context, which should be"
- + " an Activity or a WindowContext");
+ + " an Activity, WindowContext or InputMethodService");
}
mFoldingFeatureProducer.getData((features) -> {
- // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features);
consumer.accept(newWindowLayout);
});
mWindowLayoutChangeListeners.put(context, consumer);
- if (context instanceof WindowContext) {
+ // TODO(b/258065175) Further extend this to ContextWrappers.
+ if (context instanceof WindowProvider) {
final IBinder windowContextToken = context.getWindowContextToken();
- final WindowContextConfigListener listener =
- new WindowContextConfigListener(windowContextToken);
+ final ConfigurationChangeListener listener =
+ new ConfigurationChangeListener(windowContextToken);
context.registerComponentCallbacks(listener);
- mWindowContextConfigListeners.put(windowContextToken, listener);
+ mConfigurationChangeListeners.put(windowContextToken, listener);
}
}
@@ -150,10 +151,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) {
continue;
}
- if (context instanceof WindowContext) {
+ if (context instanceof WindowProvider) {
final IBinder token = context.getWindowContextToken();
- context.unregisterComponentCallbacks(mWindowContextConfigListeners.get(token));
- mWindowContextConfigListeners.remove(token);
+ context.unregisterComponentCallbacks(mConfigurationChangeListeners.get(token));
+ mConfigurationChangeListeners.remove(token);
}
break;
}
@@ -349,10 +350,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
}
- private final class WindowContextConfigListener implements ComponentCallbacks {
+ private final class ConfigurationChangeListener implements ComponentCallbacks {
final IBinder mToken;
- WindowContextConfigListener(IBinder token) {
+ ConfigurationChangeListener(IBinder token) {
mToken = token;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 5533ad56d17c..56aa742b8626 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -95,7 +95,7 @@ interface ISplitScreen {
*/
oneway void startShortcutAndTask(in ShortcutInfo shortcutInfo, in Bundle options1, int taskId,
in Bundle options2, int splitPosition, float splitRatio,
- in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
/**
* Version of startTasks using legacy transition system.
@@ -119,6 +119,21 @@ interface ISplitScreen {
in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
/**
+ * Start a pair of intents using legacy transition system.
+ */
+ oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1,
+ in Bundle options1, in PendingIntent pendingIntent2, in Bundle options2,
+ int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
+ in InstanceId instanceId) = 18;
+
+ /**
+ * Start a pair of intents in one transition.
+ */
+ oneway void startIntents(in PendingIntent pendingIntent1, in Bundle options1,
+ in PendingIntent pendingIntent2, in Bundle options2, int splitPosition,
+ float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
+
+ /**
* Blocking call that notifies and gets additional split-screen targets when entering
* recents (for example: the dividerBar).
* @param appTargets apps that will be re-parented to display area
@@ -132,4 +147,4 @@ interface ISplitScreen {
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
}
-// Last id = 17
+// Last id = 19 \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index cdc8cdd2c28d..1774dd5e3b6f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -548,6 +548,24 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
options2, splitPosition, splitRatio, remoteTransition, instanceId);
}
+ private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ Intent fillInIntent1 = null;
+ Intent fillInIntent2 = null;
+ if (launchSameComponentAdjacently(pendingIntent1, pendingIntent2)
+ && supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) {
+ fillInIntent1 = new Intent();
+ fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ fillInIntent2 = new Intent();
+ fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, options1,
+ pendingIntent2, fillInIntent2, options2, splitPosition, splitRatio, adapter,
+ instanceId);
+ }
+
@Override
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
@@ -621,6 +639,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return Objects.equals(launchingActivity, pairedActivity);
}
+ private boolean launchSameComponentAdjacently(PendingIntent pendingIntent1,
+ PendingIntent pendingIntent2) {
+ return Objects.equals(pendingIntent1.getIntent().getComponent(),
+ pendingIntent2.getIntent().getComponent());
+ }
+
@VisibleForTesting
/** Returns {@code true} if the component supports multi-instances split. */
boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
@@ -986,6 +1010,27 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
+ public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
+ executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
+ (controller) ->
+ controller.startIntentsWithLegacyTransition(
+ pendingIntent1, options1, pendingIntent2, options2, splitPosition,
+ splitRatio, adapter, instanceId)
+ );
+ }
+
+ @Override
+ public void startIntents(PendingIntent pendingIntent1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ // TODO(b/259368992): To be implemented.
+ }
+
+ @Override
public void startShortcut(String packageName, String shortcutId, int position,
@Nullable Bundle options, UserHandle user, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startShortcut",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 3bb630d81a60..acb71a80ee8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -589,8 +589,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Starts a pair of tasks using legacy transition. */
void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
addActivityOptions(options1, mSideStage);
@@ -600,7 +599,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
instanceId);
}
- /** Starts a pair of intent and task using legacy transition. */
+ /** Starts a pair of intents using legacy transition. */
+ void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, Intent fillInIntent1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2, Intent fillInIntent2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+
+ startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, options2, splitPosition,
+ splitRatio, adapter, instanceId);
+ }
+
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
@SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
@@ -628,12 +640,29 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
instanceId);
}
+ private void startWithLegacyTransition(WindowContainerTransaction wct,
+ @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
+ @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent,
+ mainOptions, sidePosition, splitRatio, adapter, instanceId);
+ }
+
+ private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
+ @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */,
+ null /* mainFillInIntent */, mainOptions, sidePosition, splitRatio, adapter,
+ instanceId);
+ }
+
/**
* @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
* {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
+ @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
// Init divider first to make divider leash for remote animation target.
@@ -702,7 +731,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mainOptions == null) mainOptions = new Bundle();
addActivityOptions(mainOptions, mMainStage);
updateWindowBounds(mSplitLayout, wct);
- wct.startTask(mainTaskId, mainOptions);
+ if (mainTaskId == INVALID_TASK_ID) {
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions);
+ } else {
+ wct.startTask(mainTaskId, mainOptions);
+ }
wct.reorder(mRootTaskInfo.token, true);
wct.setForceTranslucent(mRootTaskInfo.token, false);
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rCA/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rCA/strings.xml
new file mode 100644
index 000000000000..bc885286674a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rCA/strings.xml
@@ -0,0 +1,26 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"No apps."</string>
+ <string name="menu_show_system" msgid="906304605807554788">"Show system"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"Hide system"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string>
+ <string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rXC/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rXC/strings.xml
new file mode 100644
index 000000000000..c395286a4d7d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rXC/strings.xml
@@ -0,0 +1,26 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_applications" msgid="5800789569715871963">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‏‎‏‏‎No apps.‎‏‎‎‏‎"</string>
+ <string name="menu_show_system" msgid="906304605807554788">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‎‎‏‎‏‏‎‏‏‏‎‏‏‏‎‏‎‏‏‎‏‏‎‎‎‎‏‏‏‎‎‏‎‎‎Show system‎‏‎‎‏‎"</string>
+ <string name="menu_hide_system" msgid="374571689914923020">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‎‎‎Hide system‎‏‎‎‏‎"</string>
+ <string name="app_permission_summary_allowed" msgid="6115213465364138103">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‏‏‎‏‏‎‎‏‏‎‏‎‎‎‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‏‏‏‎Allowed‎‏‎‎‏‎"</string>
+ <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‏‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‏‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‏‎‏‏‎‎‎‎‎‎‎Not allowed‎‏‎‎‏‎"</string>
+ <string name="version_text" msgid="4001669804596458577">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‎‎‎‏‏‎‎‏‎‎‏‎‏‏‏‏‏‎‎‎‏‎‎‏‎‏‏‎‏‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎version ‎‏‎‎‏‏‎<xliff:g id="VERSION_NUM">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-en-rCA/arrays.xml b/packages/SettingsLib/res/values-en-rCA/arrays.xml
index 327e4e9990d1..8a5723231526 100644
--- a/packages/SettingsLib/res/values-en-rCA/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rCA/arrays.xml
@@ -86,7 +86,7 @@
<item msgid="8147982633566548515">"map14"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="2494959071796102843">"Use system selection (default)"</item>
+ <item msgid="2494959071796102843">"Use System Selection (Default)"</item>
<item msgid="4055460186095649420">"SBC"</item>
<item msgid="720249083677397051">"AAC"</item>
<item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
@@ -96,7 +96,7 @@
<item msgid="506175145534048710">"Opus"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="8868109554557331312">"Use system selection (default)"</item>
+ <item msgid="8868109554557331312">"Use System Selection (Default)"</item>
<item msgid="9024885861221697796">"SBC"</item>
<item msgid="4688890470703790013">"AAC"</item>
<item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
@@ -106,52 +106,52 @@
<item msgid="7940970833006181407">"Opus"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="926809261293414607">"Use system selection (default)"</item>
+ <item msgid="926809261293414607">"Use System Selection (Default)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
<item msgid="3208896645474529394">"48.0 kHz"</item>
<item msgid="8420261949134022577">"88.2 kHz"</item>
<item msgid="8887519571067543785">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="2284090879080331090">"Use system selection (default)"</item>
+ <item msgid="2284090879080331090">"Use System Selection (Default)"</item>
<item msgid="1872276250541651186">"44.1 kHz"</item>
<item msgid="8736780630001704004">"48.0 kHz"</item>
<item msgid="7698585706868856888">"88.2 kHz"</item>
<item msgid="8946330945963372966">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="2574107108483219051">"Use system selection (default)"</item>
+ <item msgid="2574107108483219051">"Use System Selection (Default)"</item>
<item msgid="4671992321419011165">"16 bits/sample"</item>
<item msgid="1933898806184763940">"24 bits/sample"</item>
<item msgid="1212577207279552119">"32 bits/sample"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="9196208128729063711">"Use system selection (default)"</item>
+ <item msgid="9196208128729063711">"Use System Selection (Default)"</item>
<item msgid="1084497364516370912">"16 bits/sample"</item>
<item msgid="2077889391457961734">"24 bits/sample"</item>
<item msgid="3836844909491316925">"32 bits/sample"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="3014194562841654656">"Use system selection (default)"</item>
+ <item msgid="3014194562841654656">"Use System Selection (Default)"</item>
<item msgid="5982952342181788248">"Mono"</item>
<item msgid="927546067692441494">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="1997302811102880485">"Use system selection (default)"</item>
+ <item msgid="1997302811102880485">"Use System Selection (Default)"</item>
<item msgid="8005696114958453588">"Mono"</item>
<item msgid="1333279807604675720">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="1241278021345116816">"Optimised for Audio Quality (990kbps/909kbps)"</item>
- <item msgid="3523665555859696539">"Balanced Audio And Connection Quality (660 kbps/606 kbps)"</item>
- <item msgid="886408010459747589">"Optimised for Connection Quality (330kbps/303kbps)"</item>
+ <item msgid="1241278021345116816">"Optimized for Audio Quality (990kbps/909kbps)"</item>
+ <item msgid="3523665555859696539">"Balanced Audio And Connection Quality (660kbps/606kbps)"</item>
+ <item msgid="886408010459747589">"Optimized for Connection Quality (330kbps/303kbps)"</item>
<item msgid="3808414041654351577">"Best Effort (Adaptive Bit Rate)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="804499336721569838">"Optimised for Audio Quality"</item>
- <item msgid="7451422070435297462">"Balanced Audio and Connection Quality"</item>
- <item msgid="6173114545795428901">"Optimised for Connection Quality"</item>
- <item msgid="4349908264188040530">"Best effort (adaptive bit rate)"</item>
+ <item msgid="804499336721569838">"Optimized for Audio Quality"</item>
+ <item msgid="7451422070435297462">"Balanced Audio And Connection Quality"</item>
+ <item msgid="6173114545795428901">"Optimized for Connection Quality"</item>
+ <item msgid="4349908264188040530">"Best Effort (Adaptive Bit Rate)"</item>
</string-array>
<string-array name="bluetooth_audio_active_device_summaries">
<item msgid="8019740759207729126"></item>
@@ -161,25 +161,25 @@
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="1191094707770726722">"Off"</item>
- <item msgid="7839165897132179888">"64 K"</item>
- <item msgid="2715700596495505626">"256 K"</item>
- <item msgid="7099386891713159947">"1 M"</item>
- <item msgid="6069075827077845520">"4 M"</item>
- <item msgid="6078203297886482480">"8 M"</item>
+ <item msgid="7839165897132179888">"64K"</item>
+ <item msgid="2715700596495505626">"256K"</item>
+ <item msgid="7099386891713159947">"1M"</item>
+ <item msgid="6069075827077845520">"4M"</item>
+ <item msgid="6078203297886482480">"8M"</item>
</string-array>
<string-array name="select_logd_size_lowram_titles">
<item msgid="1145807928339101085">"Off"</item>
- <item msgid="4064786181089783077">"64 K"</item>
- <item msgid="3052710745383602630">"256 K"</item>
- <item msgid="3691785423374588514">"1 M"</item>
+ <item msgid="4064786181089783077">"64K"</item>
+ <item msgid="3052710745383602630">"256K"</item>
+ <item msgid="3691785423374588514">"1M"</item>
</string-array>
<string-array name="select_logd_size_summaries">
<item msgid="409235464399258501">"Off"</item>
- <item msgid="4195153527464162486">"64 K per log buffer"</item>
- <item msgid="7464037639415220106">"256 K per log buffer"</item>
- <item msgid="8539423820514360724">"1 M per log buffer"</item>
- <item msgid="1984761927103140651">"4 M per log buffer"</item>
- <item msgid="2983219471251787208">"8 M per log buffer"</item>
+ <item msgid="4195153527464162486">"64K per log buffer"</item>
+ <item msgid="7464037639415220106">"256K per log buffer"</item>
+ <item msgid="8539423820514360724">"1M per log buffer"</item>
+ <item msgid="1984761927103140651">"4M per log buffer"</item>
+ <item msgid="2983219471251787208">"8M per log buffer"</item>
</string-array>
<string-array name="select_logpersist_titles">
<item msgid="704720725704372366">"Off"</item>
@@ -222,7 +222,7 @@
</string-array>
<string-array name="overlay_display_devices_entries">
<item msgid="4497393944195787240">"None"</item>
- <item msgid="8461943978957133391">"480 p"</item>
+ <item msgid="8461943978957133391">"480p"</item>
<item msgid="6923083594932909205">"480p (secure)"</item>
<item msgid="1226941831391497335">"720p"</item>
<item msgid="7051983425968643928">"720p (secure)"</item>
@@ -258,10 +258,10 @@
<string-array name="app_process_limit_entries">
<item msgid="794656271086646068">"Standard limit"</item>
<item msgid="8628438298170567201">"No background processes"</item>
- <item msgid="915752993383950932">"At most, 1 process"</item>
- <item msgid="8554877790859095133">"At most, 2 processes"</item>
- <item msgid="9060830517215174315">"At most, 3 processes"</item>
- <item msgid="6506681373060736204">"At most, 4 processes"</item>
+ <item msgid="915752993383950932">"At most 1 process"</item>
+ <item msgid="8554877790859095133">"At most 2 processes"</item>
+ <item msgid="9060830517215174315">"At most 3 processes"</item>
+ <item msgid="6506681373060736204">"At most 4 processes"</item>
</string-array>
<string-array name="usb_configuration_titles">
<item msgid="3358668781763928157">"Charging"</item>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 77b20a630461..4714a0b44cb9 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -46,8 +46,8 @@
<string name="wifi_security_passpoint" msgid="2209078477216565387">"Passpoint"</string>
<string name="wifi_security_sae" msgid="3644520541721422843">"WPA3-Personal"</string>
<string name="wifi_security_psk_sae" msgid="8135104122179904684">"WPA2/WPA3-Personal"</string>
- <string name="wifi_security_none_owe" msgid="5241745828327404101">"None/Enhanced open"</string>
- <string name="wifi_security_owe" msgid="3343421403561657809">"Enhanced open"</string>
+ <string name="wifi_security_none_owe" msgid="5241745828327404101">"None/Enhanced Open"</string>
+ <string name="wifi_security_owe" msgid="3343421403561657809">"Enhanced Open"</string>
<string name="wifi_security_eap_suiteb" msgid="415842785991698142">"WPA3-Enterprise 192-bit"</string>
<string name="wifi_remembered" msgid="3266709779723179188">"Saved"</string>
<string name="wifi_disconnected" msgid="7054450256284661757">"Disconnected"</string>
@@ -59,29 +59,29 @@
<string name="wifi_check_password_try_again" msgid="8817789642851605628">"Check password and try again"</string>
<string name="wifi_not_in_range" msgid="1541760821805777772">"Not in range"</string>
<string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Won\'t automatically connect"</string>
- <string name="wifi_no_internet" msgid="1774198889176926299">"No Internet access"</string>
+ <string name="wifi_no_internet" msgid="1774198889176926299">"No internet access"</string>
<string name="saved_network" msgid="7143698034077223645">"Saved by <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatically connected via %1$s"</string>
<string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatically connected via network rating provider"</string>
<string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string>
- <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string>
+ <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No internet"</string>
<string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string>
<string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string>
- <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string>
- <string name="wifi_status_sign_in_required" msgid="2236267500459526855">"Sign-in required"</string>
+ <string name="wifi_status_no_internet" msgid="3799933875988829048">"No internet"</string>
+ <string name="wifi_status_sign_in_required" msgid="2236267500459526855">"Sign in required"</string>
<string name="wifi_ap_unable_to_handle_new_sta" msgid="5885145407184194503">"Access point temporarily full"</string>
<string name="osu_opening_provider" msgid="4318105381295178285">"Opening <xliff:g id="PASSPOINTPROVIDER">%1$s</xliff:g>"</string>
<string name="osu_connect_failed" msgid="9107873364807159193">"Couldn’t connect"</string>
<string name="osu_completing_sign_up" msgid="8412636665040390901">"Completing sign-up…"</string>
- <string name="osu_sign_up_failed" msgid="5605453599586001793">"Couldn’t complete sign-up. Tap to try again"</string>
+ <string name="osu_sign_up_failed" msgid="5605453599586001793">"Couldn’t complete sign-up. Tap to try again."</string>
<string name="osu_sign_up_complete" msgid="7640183358878916847">"Sign-up complete. Connecting…"</string>
<string name="speed_label_slow" msgid="6069917670665664161">"Slow"</string>
<string name="speed_label_okay" msgid="1253594383880810424">"OK"</string>
<string name="speed_label_fast" msgid="2677719134596044051">"Fast"</string>
- <string name="speed_label_very_fast" msgid="8215718029533182439">"Very fast"</string>
+ <string name="speed_label_very_fast" msgid="8215718029533182439">"Very Fast"</string>
<string name="wifi_passpoint_expired" msgid="6540867261754427561">"Expired"</string>
- <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
+ <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
<string name="bluetooth_disconnected" msgid="7739366554710388701">"Disconnected"</string>
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Disconnecting…"</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"Connecting…"</string>
@@ -110,8 +110,8 @@
<string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Contacts and call history sharing"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"Use for contacts and call history sharing"</string>
<string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Internet connection sharing"</string>
- <string name="bluetooth_profile_map" msgid="8907204701162107271">"Text messages"</string>
- <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM access"</string>
+ <string name="bluetooth_profile_map" msgid="8907204701162107271">"Text Messages"</string>
+ <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM Access"</string>
<string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
<string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
<string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string>
@@ -120,14 +120,14 @@
<string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string>
<string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string>
- <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file-transfer server"</string>
+ <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file transfer server"</string>
<string name="bluetooth_map_profile_summary_connected" msgid="4141725591784669181">"Connected to map"</string>
<string name="bluetooth_sap_profile_summary_connected" msgid="1280297388033001037">"Connected to SAP"</string>
- <string name="bluetooth_opp_profile_summary_not_connected" msgid="3959741824627764954">"Not connected to file-transfer server"</string>
+ <string name="bluetooth_opp_profile_summary_not_connected" msgid="3959741824627764954">"Not connected to file transfer server"</string>
<string name="bluetooth_hid_profile_summary_connected" msgid="3923653977051684833">"Connected to input device"</string>
- <string name="bluetooth_pan_user_profile_summary_connected" msgid="380469653827505727">"Connected to device for Internet access"</string>
- <string name="bluetooth_pan_nap_profile_summary_connected" msgid="3744773111299503493">"Sharing local Internet connection with device"</string>
- <string name="bluetooth_pan_profile_summary_use_for" msgid="7422039765025340313">"Use for Internet access"</string>
+ <string name="bluetooth_pan_user_profile_summary_connected" msgid="380469653827505727">"Connected to device for internet access"</string>
+ <string name="bluetooth_pan_nap_profile_summary_connected" msgid="3744773111299503493">"Sharing local internet connection with device"</string>
+ <string name="bluetooth_pan_profile_summary_use_for" msgid="7422039765025340313">"Use for internet access"</string>
<string name="bluetooth_map_profile_summary_use_for" msgid="4453622103977592583">"Use for map"</string>
<string name="bluetooth_sap_profile_summary_use_for" msgid="6204902866176714046">"Use for SIM access"</string>
<string name="bluetooth_a2dp_profile_summary_use_for" msgid="7324694226276491807">"Use for media audio"</string>
@@ -146,17 +146,17 @@
<string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Pairing rejected by <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
<string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Computer"</string>
<string name="bluetooth_talkback_headset" msgid="3406852564400882682">"Headset"</string>
- <string name="bluetooth_talkback_phone" msgid="868393783858123880">"Telephone"</string>
+ <string name="bluetooth_talkback_phone" msgid="868393783858123880">"Phone"</string>
<string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imaging"</string>
<string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Headphone"</string>
<string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Input Peripheral"</string>
<string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
- <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi off."</string>
- <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi disconnected."</string>
- <string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"Wi-Fi one bar."</string>
- <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi two bars."</string>
- <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi three bars."</string>
- <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal full."</string>
+ <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi off."</string>
+ <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi disconnected."</string>
+ <string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"Wifi one bar."</string>
+ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi two bars."</string>
+ <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi three bars."</string>
+ <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi signal full."</string>
<string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open network"</string>
<string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Secure network"</string>
<string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -178,7 +178,7 @@
<string name="tts_default_rate_title" msgid="3964187817364304022">"Speech rate"</string>
<string name="tts_default_rate_summary" msgid="3781937042151716987">"Speed at which the text is spoken"</string>
<string name="tts_default_pitch_title" msgid="6988592215554485479">"Pitch"</string>
- <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Affects the tone of the synthesised speech"</string>
+ <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Affects the tone of the synthesized speech"</string>
<string name="tts_default_lang_title" msgid="4698933575028098940">"Language"</string>
<string name="tts_lang_use_system" msgid="6312945299804012406">"Use system language"</string>
<string name="tts_lang_not_selected" msgid="7927823081096056147">"Language not selected"</string>
@@ -188,7 +188,7 @@
<string name="tts_install_data_title" msgid="1829942496472751703">"Install voice data"</string>
<string name="tts_install_data_summary" msgid="3608874324992243851">"Install the voice data required for speech synthesis"</string>
<string name="tts_engine_security_warning" msgid="3372432853837988146">"This speech synthesis engine may be able to collect all the text that will be spoken, including personal data like passwords and credit card numbers. It comes from the <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> engine. Enable the use of this speech synthesis engine?"</string>
- <string name="tts_engine_network_required" msgid="8722087649733906851">"This language requires a working network connection for Text-to-Speech output."</string>
+ <string name="tts_engine_network_required" msgid="8722087649733906851">"This language requires a working network connection for text-to-speech output."</string>
<string name="tts_default_sample_string" msgid="6388016028292967973">"This is an example of speech synthesis"</string>
<string name="tts_status_title" msgid="8190784181389278640">"Default language status"</string>
<string name="tts_status_ok" msgid="8583076006537547379">"<xliff:g id="LOCALE">%1$s</xliff:g> is fully supported"</string>
@@ -224,7 +224,7 @@
<string name="apn_settings_not_available" msgid="1147111671403342300">"Access Point Name settings are not available for this user"</string>
<string name="enable_adb" msgid="8072776357237289039">"USB debugging"</string>
<string name="enable_adb_summary" msgid="3711526030096574316">"Debug mode when USB is connected"</string>
- <string name="clear_adb_keys" msgid="3010148733140369917">"Revoke USB debugging authorisations"</string>
+ <string name="clear_adb_keys" msgid="3010148733140369917">"Revoke USB debugging authorizations"</string>
<string name="enable_adb_wireless" msgid="6973226350963971018">"Wireless debugging"</string>
<string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Debug mode when Wi‑Fi is connected"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"Error"</string>
@@ -233,22 +233,22 @@
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Pair device with QR code"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Pair new devices using QR code scanner"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"Pair device with pairing code"</string>
- <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Pair new devices using six-digit code"</string>
+ <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Pair new devices using six digit code"</string>
<string name="adb_paired_devices_title" msgid="5268997341526217362">"Paired devices"</string>
<string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Currently connected"</string>
<string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Device details"</string>
<string name="adb_device_forget" msgid="193072400783068417">"Forget"</string>
<string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Device fingerprint: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
<string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Connection unsuccessful"</string>
- <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Make sure that <xliff:g id="DEVICE_NAME">%1$s</xliff:g> is connected to the correct network"</string>
+ <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Make sure <xliff:g id="DEVICE_NAME">%1$s</xliff:g> is connected to the correct network"</string>
<string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Pair with device"</string>
<string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Wi‑Fi pairing code"</string>
<string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"Pairing unsuccessful"</string>
- <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Make sure that the device is connected to the same network."</string>
+ <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Make sure the device is connected to the same network."</string>
<string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Pair device over Wi‑Fi by scanning a QR code"</string>
<string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Pairing device…"</string>
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Failed to pair the device. Either the QR code was incorrect, or the device is not connected to the same network."</string>
- <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address and port"</string>
+ <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address &amp; Port"</string>
<string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Scan QR code"</string>
<string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Pair device over Wi‑Fi by scanning a QR code"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Please connect to a Wi‑Fi network"</string>
@@ -268,28 +268,28 @@
<string name="mock_location_app_set" msgid="4706722469342913843">"Mock location app: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="debug_networking_category" msgid="6829757985772659599">"Networking"</string>
<string name="wifi_display_certification" msgid="1805579519992520381">"Wireless display certification"</string>
- <string name="wifi_verbose_logging" msgid="1785910450009679371">"Enable Wi‑Fi verbose logging"</string>
+ <string name="wifi_verbose_logging" msgid="1785910450009679371">"Enable Wi‑Fi Verbose Logging"</string>
<string name="wifi_scan_throttling" msgid="2985624788509913617">"Wi‑Fi scan throttling"</string>
- <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Wi‑Fi non‑persistent MAC randomisation"</string>
+ <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Wi‑Fi non‑persistent MAC randomization"</string>
<string name="mobile_data_always_on" msgid="8275958101875563572">"Mobile data always active"</string>
<string name="tethering_hardware_offload" msgid="4116053719006939161">"Tethering hardware acceleration"</string>
<string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Show Bluetooth devices without names"</string>
<string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Disable absolute volume"</string>
<string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Enable Gabeldorsche"</string>
- <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP version"</string>
+ <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP Version"</string>
<string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Select Bluetooth AVRCP Version"</string>
- <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP version"</string>
- <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Select Bluetooth MAP version"</string>
- <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth audio codec"</string>
+ <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP Version"</string>
+ <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Select Bluetooth MAP Version"</string>
+ <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth Audio Codec"</string>
<string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Trigger Bluetooth Audio Codec\nSelection"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth audio sample rate"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth Audio Sample Rate"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"Trigger Bluetooth Audio Codec\nSelection: Sample Rate"</string>
- <string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"Grey-out means not supported by phone or headset"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"Bluetooth audio bits per sample"</string>
+ <string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"Gray-out means not supported by phone or headset"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"Bluetooth Audio Bits Per Sample"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"Trigger Bluetooth Audio Codec\nSelection: Bits Per Sample"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"Bluetooth audio channel mode"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"Bluetooth Audio Channel Mode"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="2076949781460359589">"Trigger Bluetooth Audio Codec\nSelection: Channel Mode"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"Bluetooth audio LDAC codec: Playback quality"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="7274396574659784285">"Trigger Bluetooth Audio LDAC\nCodec Selection: Playback Quality"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="2040810756832027227">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="select_private_dns_configuration_title" msgid="7887550926056143018">"Private DNS"</string>
@@ -301,14 +301,14 @@
<string name="private_dns_mode_provider_failure" msgid="8356259467861515108">"Couldn\'t connect"</string>
<string name="wifi_display_certification_summary" msgid="8111151348106907513">"Show options for wireless display certification"</string>
<string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
- <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Reduces battery drain and improves network performance"</string>
- <string name="wifi_non_persistent_mac_randomization_summary" msgid="2159794543105053930">"When this mode is enabled, this device’s MAC address may change each time that it connects to a network that has MAC randomisation enabled."</string>
+ <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Reduces battery drain &amp; improves network performance"</string>
+ <string name="wifi_non_persistent_mac_randomization_summary" msgid="2159794543105053930">"When this mode is enabled, this device’s MAC address may change each time it connects to a network that has MAC randomization enabled."</string>
<string name="wifi_metered_label" msgid="8737187690304098638">"Metered"</string>
<string name="wifi_unmetered_label" msgid="6174142840934095093">"Unmetered"</string>
<string name="select_logd_size_title" msgid="1604578195914595173">"Logger buffer sizes"</string>
<string name="select_logd_size_dialog_title" msgid="2105401994681013578">"Select Logger sizes per log buffer"</string>
<string name="dev_logpersist_clear_warning_title" msgid="8631859265777337991">"Clear logger persistent storage?"</string>
- <string name="dev_logpersist_clear_warning_message" msgid="6447590867594287413">"When we are no longer monitoring with the persistent logger, we are required to erase the logger data resident on your device."</string>
+ <string name="dev_logpersist_clear_warning_message" msgid="6447590867594287413">"When we no longer are monitoring with the persistent logger, we are required to erase the logger data resident on your device."</string>
<string name="select_logpersist_title" msgid="447071974007104196">"Store logger data persistently on device"</string>
<string name="select_logpersist_dialog_title" msgid="7745193591195485594">"Select log buffers to store persistently on device"</string>
<string name="select_usb_configuration_title" msgid="6339801314922294586">"Select USB Configuration"</string>
@@ -319,22 +319,22 @@
<string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Always keep mobile data active, even when Wi‑Fi is active (for fast network switching)."</string>
<string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Use tethering hardware acceleration if available"</string>
<string name="adb_warning_title" msgid="7708653449506485728">"Allow USB debugging?"</string>
- <string name="adb_warning_message" msgid="8145270656419669221">"USB debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification and read log data."</string>
+ <string name="adb_warning_message" msgid="8145270656419669221">"USB debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data."</string>
<string name="adbwifi_warning_title" msgid="727104571653031865">"Allow wireless debugging?"</string>
<string name="adbwifi_warning_message" msgid="8005936574322702388">"Wireless debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data."</string>
- <string name="adb_keys_warning_message" msgid="2968555274488101220">"Revoke access to USB debugging from all computers you\'ve previously authorised?"</string>
+ <string name="adb_keys_warning_message" msgid="2968555274488101220">"Revoke access to USB debugging from all computers you’ve previously authorized?"</string>
<string name="dev_settings_warning_title" msgid="8251234890169074553">"Allow development settings?"</string>
<string name="dev_settings_warning_message" msgid="37741686486073668">"These settings are intended for development use only. They can cause your device and the applications on it to break or misbehave."</string>
<string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Verify apps over USB"</string>
- <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Check apps installed via ADB/ADT for harmful behaviour."</string>
+ <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Check apps installed via ADB/ADT for harmful behavior."</string>
<string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth devices without names (MAC addresses only) will be displayed"</string>
<string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control."</string>
<string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Enables the Bluetooth Gabeldorsche feature stack."</string>
- <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Enables the enhanced connectivity feature."</string>
+ <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Enables the Enhanced Connectivity feature."</string>
<string name="enable_terminal_title" msgid="3834790541986303654">"Local terminal"</string>
<string name="enable_terminal_summary" msgid="2481074834856064500">"Enable terminal app that offers local shell access"</string>
<string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP checking"</string>
- <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"Set HDCP checking behaviour"</string>
+ <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"Set HDCP checking behavior"</string>
<string name="debug_debugging_category" msgid="535341063709248842">"Debugging"</string>
<string name="debug_app" msgid="8903350241392391766">"Select debug app"</string>
<string name="debug_app_not_set" msgid="1934083001283807188">"No debug application set"</string>
@@ -363,7 +363,7 @@
<string name="debug_hw_overdraw" msgid="8944851091008756796">"Debug GPU overdraw"</string>
<string name="disable_overlays" msgid="4206590799671557143">"Disable HW overlays"</string>
<string name="disable_overlays_summary" msgid="1954852414363338166">"Always use GPU for screen compositing"</string>
- <string name="simulate_color_space" msgid="1206503300335835151">"Simulate colour space"</string>
+ <string name="simulate_color_space" msgid="1206503300335835151">"Simulate color space"</string>
<string name="enable_opengl_traces_title" msgid="4638773318659125196">"Enable OpenGL traces"</string>
<string name="usb_audio_disable_routing" msgid="3367656923544254975">"Disable USB audio routing"</string>
<string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Disable automatic routing to USB audio peripherals"</string>
@@ -379,31 +379,31 @@
<string name="enable_gpu_debug_layers" msgid="4986675516188740397">"Enable GPU debug layers"</string>
<string name="enable_gpu_debug_layers_summary" msgid="4921521407377170481">"Allow loading GPU debug layers for debug apps"</string>
<string name="enable_verbose_vendor_logging" msgid="1196698788267682072">"Enable verbose vendor logging"</string>
- <string name="enable_verbose_vendor_logging_summary" msgid="5426292185780393708">"Include additional device-specific vendor logs in bug reports, which may contain private information, use more battery and/or use more storage."</string>
+ <string name="enable_verbose_vendor_logging_summary" msgid="5426292185780393708">"Include additional device-specific vendor logs in bug reports, which may contain private information, use more battery, and/or use more storage."</string>
<string name="window_animation_scale_title" msgid="5236381298376812508">"Window animation scale"</string>
<string name="transition_animation_scale_title" msgid="1278477690695439337">"Transition animation scale"</string>
<string name="animator_duration_scale_title" msgid="7082913931326085176">"Animator duration scale"</string>
<string name="overlay_display_devices_title" msgid="5411894622334469607">"Simulate secondary displays"</string>
<string name="debug_applications_category" msgid="5394089406638954196">"Apps"</string>
- <string name="immediately_destroy_activities" msgid="1826287490705167403">"Don\'t keep activities"</string>
+ <string name="immediately_destroy_activities" msgid="1826287490705167403">"Don’t keep activities"</string>
<string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"Destroy every activity as soon as the user leaves it"</string>
<string name="app_process_limit_title" msgid="8361367869453043007">"Background process limit"</string>
<string name="show_all_anrs" msgid="9160563836616468726">"Show background ANRs"</string>
- <string name="show_all_anrs_summary" msgid="8562788834431971392">"Display App Not Responding dialogue for background apps"</string>
+ <string name="show_all_anrs_summary" msgid="8562788834431971392">"Display App Not Responding dialog for background apps"</string>
<string name="show_notification_channel_warnings" msgid="3448282400127597331">"Show notification channel warnings"</string>
<string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"Displays on-screen warning when an app posts a notification without a valid channel"</string>
<string name="force_allow_on_external" msgid="9187902444231637880">"Force allow apps on external"</string>
<string name="force_allow_on_external_summary" msgid="8525425782530728238">"Makes any app eligible to be written to external storage, regardless of manifest values"</string>
- <string name="force_resizable_activities" msgid="7143612144399959606">"Force activities to be resizeable"</string>
- <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizeable for multi-window, regardless of manifest values."</string>
+ <string name="force_resizable_activities" msgid="7143612144399959606">"Force activities to be resizable"</string>
+ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizable for multi-window, regardless of manifest values."</string>
<string name="enable_freeform_support" msgid="7599125687603914253">"Enable freeform windows"</string>
<string name="enable_freeform_support_summary" msgid="1822862728719276331">"Enable support for experimental freeform windows."</string>
<string name="desktop_mode" msgid="2389067840550544462">"Desktop mode"</string>
<string name="local_backup_password_title" msgid="4631017948933578709">"Desktop backup password"</string>
- <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren\'t currently protected"</string>
+ <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren’t currently protected"</string>
<string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tap to change or remove the password for desktop full backups"</string>
<string name="local_backup_password_toast_success" msgid="4891666204428091604">"New backup password set"</string>
- <string name="local_backup_password_toast_confirmation_mismatch" msgid="2994718182129097733">"New password and confirmation don\'t match"</string>
+ <string name="local_backup_password_toast_confirmation_mismatch" msgid="2994718182129097733">"New password and confirmation don’t match"</string>
<string name="local_backup_password_toast_validation_failure" msgid="714669442363647122">"Failure setting backup password"</string>
<string name="loading_injected_setting_summary" msgid="8394446285689070348">"Loading…"</string>
<string-array name="color_mode_names">
@@ -412,9 +412,9 @@
<item msgid="6564241960833766170">"Standard"</item>
</string-array>
<string-array name="color_mode_descriptions">
- <item msgid="6828141153199944847">"Enhanced colours"</item>
- <item msgid="4548987861791236754">"Natural colours as seen by the eye"</item>
- <item msgid="1282170165150762976">"Colours optimised for digital content"</item>
+ <item msgid="6828141153199944847">"Enhanced colors"</item>
+ <item msgid="4548987861791236754">"Natural colors as seen by the eye"</item>
+ <item msgid="1282170165150762976">"Colors optimized for digital content"</item>
</string-array>
<string name="inactive_apps_title" msgid="5372523625297212320">"Standby apps"</string>
<string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Inactive. Tap to toggle."</string>
@@ -431,15 +431,15 @@
<string name="select_webview_provider_title" msgid="3917815648099445503">"WebView implementation"</string>
<string name="select_webview_provider_dialog_title" msgid="2444261109877277714">"Set WebView implementation"</string>
<string name="select_webview_provider_toast_text" msgid="8512254949169359848">"This choice is no longer valid. Try again."</string>
- <string name="picture_color_mode" msgid="1013807330552931903">"Picture colour mode"</string>
+ <string name="picture_color_mode" msgid="1013807330552931903">"Picture color mode"</string>
<string name="picture_color_mode_desc" msgid="151780973768136200">"Use sRGB"</string>
<string name="daltonizer_mode_disabled" msgid="403424372812399228">"Disabled"</string>
<string name="daltonizer_mode_monochromacy" msgid="362060873835885014">"Monochromacy"</string>
<string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Deuteranomaly (red-green)"</string>
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string>
- <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string>
- <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Colour correction can be helpful when you want to:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;See colours more accurately&lt;/li&gt; &lt;li&gt;&amp;nbsp;Remove colours to help you focus&lt;/li&gt; &lt;/ol&gt;"</string>
+ <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Color correction"</string>
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Color correction can be helpful when you want to:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;See colors more accurately&lt;/li&gt; &lt;li&gt;&amp;nbsp;Remove colors to help you focus&lt;/li&gt; &lt;/ol&gt;"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
@@ -466,7 +466,7 @@
<string name="power_remaining_duration_shutdown_imminent" product="device" msgid="4374784375644214578">"Device may shut down soon (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
<string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
- <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
+ <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
<string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
@@ -477,9 +477,9 @@
<string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string>
<string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connected, not charging"</string>
<string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
- <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully charged"</string>
+ <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully Charged"</string>
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlled by admin"</string>
- <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by restricted setting"</string>
+ <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by Restricted Setting"</string>
<string name="disabled" msgid="8017887509554714950">"Disabled"</string>
<string name="external_source_trusted" msgid="1146522036773132905">"Allowed"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Not allowed"</string>
@@ -505,13 +505,13 @@
<string name="active_input_method_subtypes" msgid="4232680535471633046">"Active input methods"</string>
<string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Use system languages"</string>
<string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"Failed to open settings for <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
- <string name="ime_security_warning" msgid="6547562217880551450">"This input method may be able to collect all the text that you type, including personal data like passwords and credit card numbers. It comes from the app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Use this input method?"</string>
+ <string name="ime_security_warning" msgid="6547562217880551450">"This input method may be able to collect all the text you type, including personal data like passwords and credit card numbers. It comes from the app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Use this input method?"</string>
<string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"Note: After a reboot, this app can\'t start until you unlock your phone"</string>
<string name="ims_reg_title" msgid="8197592958123671062">"IMS registration state"</string>
<string name="ims_reg_status_registered" msgid="884916398194885457">"Registered"</string>
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Not registered"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Unavailable"</string>
- <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomised"</string>
+ <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomized"</string>
<string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{1 device connected}other{# devices connected}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"More time."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Less time."</string>
@@ -520,7 +520,7 @@
<string name="done" msgid="381184316122520313">"Done"</string>
<string name="alarms_and_reminders_label" msgid="6918395649731424294">"Alarms and reminders"</string>
<string name="alarms_and_reminders_switch_title" msgid="4939393911531826222">"Allow setting alarms and reminders"</string>
- <string name="alarms_and_reminders_title" msgid="8819933264635406032">"Alarms and reminders"</string>
+ <string name="alarms_and_reminders_title" msgid="8819933264635406032">"Alarms &amp; reminders"</string>
<string name="alarms_and_reminders_footer_title" msgid="6302587438389079695">"Allow this app to set alarms and schedule time-sensitive actions. This lets the app run in the background, which may use more battery.\n\nIf this permission is off, existing alarms and time-based events scheduled by this app won’t work."</string>
<string name="keywords_alarms_and_reminders" msgid="6633360095891110611">"schedule, alarm, reminder, clock"</string>
<string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"Turn on"</string>
@@ -539,7 +539,7 @@
<string name="media_transfer_this_device_name" product="default" msgid="2357329267148436433">"This phone"</string>
<string name="media_transfer_this_device_name" product="tablet" msgid="3714653244000242800">"This tablet"</string>
<string name="media_transfer_this_phone" msgid="7194341457812151531">"This phone"</string>
- <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off and back on"</string>
+ <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off &amp; back on"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired audio device"</string>
<string name="help_label" msgid="3528360748637781274">"Help and feedback"</string>
<string name="storage_category" msgid="2287342585424631813">"Storage"</string>
@@ -548,23 +548,23 @@
<string name="shared_data_no_blobs_text" msgid="3108114670341737434">"There is no shared data for this user."</string>
<string name="shared_data_query_failure_text" msgid="3489828881998773687">"There was an error fetching shared data. Try again."</string>
<string name="blob_id_text" msgid="8680078988996308061">"Shared data ID: <xliff:g id="BLOB_ID">%d</xliff:g>"</string>
- <string name="blob_expires_text" msgid="7882727111491739331">"Expires on <xliff:g id="DATE">%s</xliff:g>"</string>
+ <string name="blob_expires_text" msgid="7882727111491739331">"Expires at <xliff:g id="DATE">%s</xliff:g>"</string>
<string name="shared_data_delete_failure_text" msgid="3842701391009628947">"There was an error deleting the shared data."</string>
<string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"There are no leases acquired for this shared data. Would you like to delete it?"</string>
<string name="accessor_info_title" msgid="8289823651512477787">"Apps sharing data"</string>
<string name="accessor_no_description_text" msgid="7510967452505591456">"No description provided by the app."</string>
- <string name="accessor_expires_text" msgid="4625619273236786252">"Lease expires on <xliff:g id="DATE">%s</xliff:g>"</string>
+ <string name="accessor_expires_text" msgid="4625619273236786252">"Lease expires at <xliff:g id="DATE">%s</xliff:g>"</string>
<string name="delete_blob_text" msgid="2819192607255625697">"Delete shared data"</string>
- <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"Are you sure that you want to delete this shared data?"</string>
+ <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"Are you sure you want to delete this shared data?"</string>
<string name="user_add_user_item_summary" msgid="5748424612724703400">"Users have their own apps and content"</string>
<string name="user_add_profile_item_summary" msgid="5418602404308968028">"You can restrict access to apps and content from your account"</string>
<string name="user_add_user_item_title" msgid="2394272381086965029">"User"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Restricted profile"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"Add new user?"</string>
- <string name="user_add_user_message_long" msgid="1527434966294733380">"You can share this device with other people by creating additional users. Each user has their own space, which they can customise with apps, wallpaper and so on. Users can also adjust device settings such as Wi‑Fi that affect everyone.\n\nWhen you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. Accessibility settings and services may not transfer to the new user."</string>
+ <string name="user_add_user_message_long" msgid="1527434966294733380">"You can share this device with other people by creating additional users. Each user has their own space, which they can customize with apps, wallpaper, and so on. Users can also adjust device settings like Wi‑Fi that affect everyone.\n\nWhen you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. Accessibility settings and services may not transfer to the new user."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Set up user now?"</string>
- <string name="user_setup_dialog_message" msgid="269931619868102841">"Make sure that the person is available to take the device and set up their space."</string>
+ <string name="user_setup_dialog_message" msgid="269931619868102841">"Make sure the person is available to take the device and set up their space"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Set up profile now?"</string>
<string name="user_setup_button_setup_now" msgid="1708269547187760639">"Set up now"</string>
<string name="user_setup_button_setup_later" msgid="8712980133555493516">"Not now"</string>
@@ -573,7 +573,7 @@
<string name="user_new_profile_name" msgid="2405500423304678841">"New profile"</string>
<string name="user_info_settings_title" msgid="6351390762733279907">"User info"</string>
<string name="profile_info_settings_title" msgid="105699672534365099">"Profile info"</string>
- <string name="user_need_lock_message" msgid="4311424336209509301">"Before you can create a restricted profile, you\'ll need to set up a screen lock to protect your apps and personal data."</string>
+ <string name="user_need_lock_message" msgid="4311424336209509301">"Before you can create a restricted profile, you’ll need to set up a screen lock to protect your apps and personal data."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Set lock"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Switch to <xliff:g id="USER_NAME">%s</xliff:g>"</string>
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
@@ -616,7 +616,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
- <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphones"</string>
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"On"</string>
<string name="wifi_hotspot_switch_off_text" msgid="7245567251496959764">"Off"</string>
<string name="carrier_network_change_mode" msgid="4257621815706644026">"Carrier network changing"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 1c4e4a05d517..b1ef50af9535 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -174,7 +174,7 @@
<string name="launch_defaults_some" msgid="3631650616557252926">"कुछ डिफ़ॉल्‍ट सेट हैं"</string>
<string name="launch_defaults_none" msgid="8049374306261262709">"कोई डिफ़ॉल्‍ट सेट नहीं है"</string>
<string name="tts_settings" msgid="8130616705989351312">"लेख से बोली सेटिंग"</string>
- <string name="tts_settings_title" msgid="7602210956640483039">"लिखाई को बोली में बदलना"</string>
+ <string name="tts_settings_title" msgid="7602210956640483039">"लिखाई को बोली में बदलने की सुविधा"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"बोली दर"</string>
<string name="tts_default_rate_summary" msgid="3781937042151716987">"बोलने की गति तय करें"</string>
<string name="tts_default_pitch_title" msgid="6988592215554485479">"पिच"</string>
diff --git a/packages/SettingsLib/res/values-nb/arrays.xml b/packages/SettingsLib/res/values-nb/arrays.xml
index 7e65fa0d42df..928ebc3b413e 100644
--- a/packages/SettingsLib/res/values-nb/arrays.xml
+++ b/packages/SettingsLib/res/values-nb/arrays.xml
@@ -49,9 +49,9 @@
<item msgid="1999413958589971747">"Unngår dårlig tilkobling midlertidig"</item>
</string-array>
<string-array name="hdcp_checking_titles">
- <item msgid="2377230797542526134">"Kontrollér aldri"</item>
- <item msgid="3919638466823112484">"Kontrollér kun DRM-innhold"</item>
- <item msgid="9048424957228926377">"Kontrollér alltid"</item>
+ <item msgid="2377230797542526134">"Kontroller aldri"</item>
+ <item msgid="3919638466823112484">"Kontroller kun DRM-innhold"</item>
+ <item msgid="9048424957228926377">"Kontroller alltid"</item>
</string-array>
<string-array name="hdcp_checking_summaries">
<item msgid="4045840870658484038">"Bruk aldri HDCP-kontroll"</item>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index c292e8817c69..59a2517db38c 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -427,7 +427,7 @@
<string name="transcode_notification" msgid="5560515979793436168">"Vis omkodingsvarsler"</string>
<string name="transcode_disable_cache" msgid="3160069309377467045">"Slå av omkodingsbuffer"</string>
<string name="runningservices_settings_title" msgid="6460099290493086515">"Aktive tjenester"</string>
- <string name="runningservices_settings_summary" msgid="1046080643262665743">"Se og kontrollér tjenester som kjører"</string>
+ <string name="runningservices_settings_summary" msgid="1046080643262665743">"Se og kontroller tjenester som kjører"</string>
<string name="select_webview_provider_title" msgid="3917815648099445503">"WebView-implementering"</string>
<string name="select_webview_provider_dialog_title" msgid="2444261109877277714">"Angi WebView-implementering"</string>
<string name="select_webview_provider_toast_text" msgid="8512254949169359848">"Dette valget er ikke gyldig lenger. Prøv på nytt."</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index a822e185479a..edaa0fb5de4f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -10,8 +10,6 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.Signature;
import android.content.pm.UserInfo;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -62,7 +60,6 @@ public class Utils {
static final String STORAGE_MANAGER_ENABLED_PROPERTY =
"ro.storage_manager.enabled";
- private static Signature[] sSystemSignature;
private static String sPermissionControllerPackageName;
private static String sServicesSystemSharedLibPackageName;
private static String sSharedSystemSharedLibPackageName;
@@ -374,13 +371,21 @@ public class Utils {
}
/**
+ * @deprecated Use {@link #isSystemPackage(Resources, PackageManager, ApplicationInfo)} instead.
+ */
+ @Deprecated
+ public static boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo pkg) {
+ return pkg.applicationInfo != null && isSystemPackage(resources, pm, pkg.applicationInfo);
+ }
+
+ /**
* Determine whether a package is a "system package", in which case certain things (like
* disabling notifications or disabling the package altogether) should be disallowed.
+ *
+ * Note: This function is just for UI treatment, and should not be used for security purposes.
*/
- public static boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo pkg) {
- if (sSystemSignature == null) {
- sSystemSignature = new Signature[]{getSystemSignature(pm)};
- }
+ public static boolean isSystemPackage(
+ Resources resources, PackageManager pm, @NonNull ApplicationInfo app) {
if (sPermissionControllerPackageName == null) {
sPermissionControllerPackageName = pm.getPermissionControllerPackageName();
}
@@ -390,29 +395,12 @@ public class Utils {
if (sSharedSystemSharedLibPackageName == null) {
sSharedSystemSharedLibPackageName = pm.getSharedSystemSharedLibraryPackageName();
}
- return (sSystemSignature[0] != null
- && sSystemSignature[0].equals(getFirstSignature(pkg)))
- || pkg.packageName.equals(sPermissionControllerPackageName)
- || pkg.packageName.equals(sServicesSystemSharedLibPackageName)
- || pkg.packageName.equals(sSharedSystemSharedLibPackageName)
- || pkg.packageName.equals(PrintManager.PRINT_SPOOLER_PACKAGE_NAME)
- || isDeviceProvisioningPackage(resources, pkg.packageName);
- }
-
- private static Signature getFirstSignature(PackageInfo pkg) {
- if (pkg != null && pkg.signatures != null && pkg.signatures.length > 0) {
- return pkg.signatures[0];
- }
- return null;
- }
-
- private static Signature getSystemSignature(PackageManager pm) {
- try {
- final PackageInfo sys = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES);
- return getFirstSignature(sys);
- } catch (NameNotFoundException e) {
- }
- return null;
+ return app.isSignedWithPlatformKey()
+ || app.packageName.equals(sPermissionControllerPackageName)
+ || app.packageName.equals(sServicesSystemSharedLibPackageName)
+ || app.packageName.equals(sSharedSystemSharedLibPackageName)
+ || app.packageName.equals(PrintManager.PRINT_SPOOLER_PACKAGE_NAME)
+ || isDeviceProvisioningPackage(resources, app.packageName);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 950ee21ae7b5..9583a59148fc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -668,6 +668,12 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
* @param bluetoothProfile the Bluetooth profile
*/
public void onActiveDeviceChanged(boolean isActive, int bluetoothProfile) {
+ if (BluetoothUtils.D) {
+ Log.d(TAG, "onActiveDeviceChanged: "
+ + "profile " + BluetoothProfile.getProfileName(bluetoothProfile)
+ + ", device " + mDevice.getAnonymizedAddress()
+ + ", isActive " + isActive);
+ }
boolean changed = false;
switch (bluetoothProfile) {
case BluetoothProfile.A2DP:
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index fa96a2f0ee7f..0b7b2f935e91 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -112,10 +112,10 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.util.FrameworkStatsLog;
import com.android.providers.settings.SettingsState.Setting;
-import libcore.util.HexEncoding;
-
import com.google.android.collect.Sets;
+import libcore.util.HexEncoding;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
@@ -1144,7 +1144,7 @@ public class SettingsProvider extends ContentProvider {
Slog.v(LOG_TAG, "getConfigSetting(" + name + ")");
}
- DeviceConfig.enforceReadPermission(getContext(), /*namespace=*/name.split("/")[0]);
+ DeviceConfig.enforceReadPermission(/*namespace=*/name.split("/")[0]);
// Get the value.
synchronized (mLock) {
@@ -1317,7 +1317,7 @@ public class SettingsProvider extends ContentProvider {
Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
}
- DeviceConfig.enforceReadPermission(getContext(),
+ DeviceConfig.enforceReadPermission(
prefix != null ? prefix.split("/")[0] : null);
synchronized (mLock) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 2e4a245df6a6..01c080990cfd 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -771,6 +771,9 @@
<!-- Permissions required for CTS test - CtsAppFgsTestCases -->
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
+ <!-- Permission required for CTS test - ApplicationExemptionsTests -->
+ <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
index 1d808ba7ee16..74e6d85f5374 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
@@ -59,11 +59,11 @@ class BindServiceOnMainThreadDetector : Detector(), SourceCodeScanner {
!hasWorkerThreadAnnotation(context, node.getParentOfType(UClass::class.java))
) {
context.report(
- ISSUE,
- method,
- context.getLocation(node),
- "This method should be annotated with `@WorkerThread` because " +
- "it calls ${method.name}",
+ issue = ISSUE,
+ location = context.getLocation(node),
+ message =
+ "This method should be annotated with `@WorkerThread` because " +
+ "it calls ${method.name}",
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
index 112992913661..344d0a3f3187 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
@@ -52,10 +52,9 @@ class BroadcastSentViaContextDetector : Detector(), SourceCodeScanner {
val evaluator = context.evaluator
if (evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "`Context.${method.name}()` should be replaced with " +
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "`Context.${method.name}()` should be replaced with " +
"`BroadcastSender.${method.name}()`"
)
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
index bab76ab4bce2..14099ebef56c 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
@@ -38,10 +38,9 @@ class NonInjectedMainThreadDetector : Detector(), SourceCodeScanner {
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Replace with injected `@Main Executor`."
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "Replace with injected `@Main Executor`."
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
index b62290025437..aa4b2f766bf0 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
@@ -44,11 +44,11 @@ class NonInjectedServiceDetector : Detector(), SourceCodeScanner {
method.containingClass?.qualifiedName == CLASS_CONTEXT
) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Use `@Inject` to get system-level service handles instead of " +
- "`Context.getSystemService()`"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message =
+ "Use `@Inject` to get system-level service handles instead of " +
+ "`Context.getSystemService()`"
)
} else if (
evaluator.isStatic(method) &&
@@ -56,10 +56,10 @@ class NonInjectedServiceDetector : Detector(), SourceCodeScanner {
method.containingClass?.qualifiedName == "android.accounts.AccountManager"
) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Replace `AccountManager.get()` with an injected instance of `AccountManager`"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message =
+ "Replace `AccountManager.get()` with an injected instance of `AccountManager`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index 4ba3afc7f7e2..5840e8f8dfb6 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -38,10 +38,10 @@ class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner {
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Register `BroadcastReceiver` using `BroadcastDispatcher` instead of `Context`"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "Register `BroadcastReceiver` using `BroadcastDispatcher` instead " +
+ "of `Context`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
index 7be21a512f89..b15a41b226df 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
@@ -46,10 +46,10 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner {
method.containingClass?.qualifiedName == "android.app.ActivityManager"
) {
context.report(
- ISSUE_SLOW_USER_ID_QUERY,
- method,
- context.getNameLocation(node),
- "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`"
+ issue = ISSUE_SLOW_USER_ID_QUERY,
+ location = context.getNameLocation(node),
+ message =
+ "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`"
)
}
if (
@@ -58,10 +58,9 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner {
method.containingClass?.qualifiedName == "android.os.UserManager"
) {
context.report(
- ISSUE_SLOW_USER_INFO_QUERY,
- method,
- context.getNameLocation(node),
- "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`"
+ issue = ISSUE_SLOW_USER_INFO_QUERY,
+ location = context.getNameLocation(node),
+ message = "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
index 4b9aa13c0240..bf025894d66f 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
@@ -44,10 +44,9 @@ class SoftwareBitmapDetector : Detector(), SourceCodeScanner {
val evaluator = context.evaluator
if (evaluator.isMemberInClass(referenced as? PsiField, "android.graphics.Bitmap.Config")) {
context.report(
- ISSUE,
- referenced,
- context.getNameLocation(reference),
- "Replace software bitmap with `Config.HARDWARE`"
+ issue = ISSUE,
+ location = context.getNameLocation(reference),
+ message = "Replace software bitmap with `Config.HARDWARE`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
index 1db072548a76..22f15bdcb5bd 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
@@ -66,10 +66,9 @@ class StaticSettingsProviderDetector : Detector(), SourceCodeScanner {
val subclassName = className.substring(CLASS_SETTINGS.length + 1)
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "`@Inject` a ${subclassName}Settings instead"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "`@Inject` a ${subclassName}Settings instead"
)
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
index c35ac61a6543..426211e0f327 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
@@ -126,6 +126,32 @@ class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
}
@Test
+ fun testSuppressUnbindService() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.content.ServiceConnection;
+
+ @SuppressLint("BindServiceOnMainThread")
+ public class TestClass {
+ public void unbind(Context context, ServiceConnection connection) {
+ context.unbindService(connection);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceOnMainThreadDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testWorkerMethod() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index 376acb56fac9..30b68f7e7a75 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -129,6 +129,34 @@ class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
}
@Test
+ fun testSuppressSendBroadcastInActivity() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.app.Activity;
+ import android.os.UserHandle;
+
+ public class TestClass {
+ @SuppressWarnings("BroadcastSentViaContext")
+ public void send(Activity activity) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission");
+ }
+
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BroadcastSentViaContextDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testSendBroadcastInBroadcastSender() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index 301c338f9b42..ed3d14a1f33f 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
@@ -61,6 +61,32 @@ class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
}
@Test
+ fun testSuppressGetMainThreadHandler() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.Handler;
+
+ @SuppressWarnings("NonInjectedMainThread")
+ public class TestClass {
+ public void test(Context context) {
+ Handler mainThreadHandler = context.getMainThreadHandler();
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(NonInjectedMainThreadDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testGetMainLooper() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
index 0a74bfcfee57..846129aa12c1 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -91,6 +91,32 @@ class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
}
@Test
+ fun testSuppressGetServiceWithClass() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.UserManager;
+
+ public class TestClass {
+ @SuppressLint("NonInjectedService")
+ public void getSystemServiceWithoutDagger(Context context) {
+ context.getSystemService(UserManager.class);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(NonInjectedServiceDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testGetAccountManager() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index 9ed7aa029b1d..0ac8f8e7c672 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -63,6 +63,34 @@ class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
}
@Test
+ fun testSuppressRegisterReceiver() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.BroadcastReceiver;
+ import android.content.Context;
+ import android.content.IntentFilter;
+
+ @SuppressWarnings("RegisterReceiverViaContext")
+ public class TestClass {
+ public void bind(Context context, BroadcastReceiver receiver,
+ IntentFilter filter) {
+ context.registerReceiver(receiver, filter, 0);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testRegisterReceiverAsUser() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index 54cac7b35598..34a424918a79 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -76,7 +76,7 @@ class SlowUserQueryDetectorTest : SystemUILintDetectorTest() {
import android.os.UserManager;
public class TestClass {
- public void slewlyGetUserInfo(UserManager userManager) {
+ public void slowlyGetUserInfo(UserManager userManager) {
userManager.getUserInfo();
}
}
@@ -101,6 +101,34 @@ class SlowUserQueryDetectorTest : SystemUILintDetectorTest() {
}
@Test
+ fun testSuppressGetUserInfo() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.os.UserManager;
+
+ public class TestClass {
+ @SuppressWarnings("SlowUserInfoQuery")
+ public void slowlyGetUserInfo(UserManager userManager) {
+ userManager.getUserInfo();
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(
+ SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
+ SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY
+ )
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testUserTrackerGetUserId() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index c632636eb9c8..34becc6a5b04 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -63,6 +63,31 @@ class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
}
@Test
+ fun testSuppressSoftwareBitmap() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ import android.graphics.Bitmap;
+
+ @SuppressWarnings("SoftwareBitmap")
+ public class TestClass {
+ public void test() {
+ Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565);
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(SoftwareBitmapDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testHardwareBitmap() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
index b83ed7067bc3..efe4c90ec44f 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
@@ -28,7 +28,7 @@ class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() {
override fun getIssues(): List<Issue> = listOf(StaticSettingsProviderDetector.ISSUE)
@Test
- fun testGetServiceWithString() {
+ fun testSuppressGetServiceWithString() {
lint()
.files(
TestFiles.java(
@@ -204,5 +204,34 @@ class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() {
)
}
+ @Test
+ fun testGetServiceWithString() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+
+ import android.provider.Settings;
+ import android.provider.Settings.Global;
+ import android.provider.Settings.Secure;
+
+ public class TestClass {
+ @SuppressWarnings("StaticSettingsProvider")
+ public void getSystemServiceWithoutDagger(Context context) {
+ final ContentResolver cr = mContext.getContentResolver();
+ Global.getFloat(cr, Settings.Global.UNLOCK_SOUND);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(StaticSettingsProviderDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
private val stubs = androidStubs
}
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index a3b4b385f5bd..69767867ebd7 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -25,9 +25,15 @@
-keep class ** extends androidx.preference.PreferenceFragment
-keep class com.android.systemui.tuner.*
+
+# The plugins and animation subpackages both act as shared libraries that might be referenced in
+# dynamically-loaded plugin APKs.
-keep class com.android.systemui.plugins.** {
*;
}
+-keep class !com.android.systemui.animation.R$**,com.android.systemui.animation.** {
+ *;
+}
-keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
*;
}
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index c8ba237088f2..a948c04020f3 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -23,7 +23,7 @@
<string name="keyguard_enter_your_pin" msgid="5429932527814874032">"Enter your PIN"</string>
<string name="keyguard_enter_your_pattern" msgid="351503370332324745">"Enter your pattern"</string>
<string name="keyguard_enter_your_password" msgid="7225626204122735501">"Enter your password"</string>
- <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Invalid card."</string>
+ <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Invalid Card."</string>
<string name="keyguard_charged" msgid="5478247181205188995">"Charged"</string>
<string name="keyguard_plugged_in_wireless" msgid="2537874724955057383">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging wirelessly"</string>
<string name="keyguard_plugged_in_dock" msgid="2122073051904360987">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging"</string>
@@ -70,7 +70,7 @@
<string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"Incorrect SIM PIN code you must now contact your carrier to unlock your device."</string>
<string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{Incorrect SIM PIN code, you have # remaining attempt before you must contact your carrier to unlock your device.}other{Incorrect SIM PIN code, you have # remaining attempts. }}"</string>
<string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"SIM is unusable. Contact your carrier."</string>
- <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Incorrect SIM PUK code; you have # remaining attempt before SIM becomes permanently unusable.}other{Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}}"</string>
+ <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Incorrect SIM PUK code, you have # remaining attempt before SIM becomes permanently unusable.}other{Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}}"</string>
<string name="kg_password_pin_failed" msgid="5136259126330604009">"SIM PIN operation failed!"</string>
<string name="kg_password_puk_failed" msgid="6778867411556937118">"SIM PUK operation failed!"</string>
<string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Switch input method"</string>
@@ -83,12 +83,12 @@
<string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"For additional security, use password instead"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
- <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
+ <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognized"</string>
<string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"To use Face Unlock, turn on camera access in Settings"</string>
<string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Enter SIM PIN. You have # remaining attempt before you must contact your carrier to unlock your device.}other{Enter SIM PIN. You have # remaining attempts.}}"</string>
<string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{SIM is now disabled. Enter PUK code to continue. You have # remaining attempt before SIM becomes permanently unusable. Contact carrier for details.}other{SIM is now disabled. Enter PUK code to continue. You have # remaining attempts before SIM becomes permanently unusable. Contact carrier for details.}}"</string>
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
- <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
<string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Unlock your device to continue"</string>
</resources>
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index d27fa192e741..8b8594032816 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -34,30 +34,13 @@
android:paddingTop="@dimen/status_bar_padding_top"
android:layout_alignParentEnd="true"
android:gravity="center_vertical|end" >
- <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+
+ <include
android:id="@+id/user_switcher_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="horizontal"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:background="@drawable/status_bar_user_chip_bg"
- android:visibility="visible" >
- <ImageView android:id="@+id/current_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_keyguard_size"
- android:layout_height="@dimen/multi_user_avatar_keyguard_size"
- android:scaleType="centerInside"
- android:paddingEnd="4dp" />
-
- <TextView android:id="@+id/current_user_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
- />
- </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+ android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+ layout="@layout/status_bar_user_chip_container" />
<FrameLayout android:id="@+id/system_icons_container"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 80e65a3b3295..f7600e606731 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -136,31 +136,12 @@
android:gravity="center_vertical|end"
android:clipChildren="false">
- <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+ <include
android:id="@+id/user_switcher_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="horizontal"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:layout_marginEnd="16dp"
- android:background="@drawable/status_bar_user_chip_bg"
- android:visibility="visible" >
- <ImageView android:id="@+id/current_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_keyguard_size"
- android:layout_height="@dimen/multi_user_avatar_keyguard_size"
- android:scaleType="centerInside"
- android:paddingEnd="4dp" />
-
- <TextView android:id="@+id/current_user_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
- />
- </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+ android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+ layout="@layout/status_bar_user_chip_container" />
<include layout="@layout/system_icons" />
</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_user_chip_container.xml b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
new file mode 100644
index 000000000000..b374074958cb
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
@@ -0,0 +1,40 @@
+<?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.
+ -->
+
+<com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/user_switcher_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+ android:background="@drawable/status_bar_user_chip_bg"
+ android:visibility="visible" >
+ <ImageView android:id="@+id/current_user_avatar"
+ android:layout_width="@dimen/status_bar_user_chip_avatar_size"
+ android:layout_height="@dimen/status_bar_user_chip_avatar_size"
+ android:layout_margin="4dp"
+ android:scaleType="centerInside" />
+
+ <TextView android:id="@+id/current_user_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="8dp"
+ android:textAppearance="@style/TextAppearance.StatusBar.UserChip"
+ />
+</com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
diff --git a/packages/SystemUI/res/values-en-rCA/strings_tv.xml b/packages/SystemUI/res/values-en-rCA/strings_tv.xml
index e97dbe4bd491..a62884693ed8 100644
--- a/packages/SystemUI/res/values-en-rCA/strings_tv.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings_tv.xml
@@ -23,13 +23,13 @@
<string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN is disconnected"</string>
<string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
<string name="tv_notification_panel_title" msgid="5311050946506276154">"Notifications"</string>
- <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No notifications"</string>
+ <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No Notifications"</string>
<string name="mic_recording_announcement" msgid="7587123608060316575">"Microphone is recording"</string>
<string name="camera_recording_announcement" msgid="7240177719403759112">"Camera is recording"</string>
- <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera and microphone are recording"</string>
+ <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera and Microphone are recording"</string>
<string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Microphone stopped recording"</string>
<string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Camera stopped recording"</string>
- <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera and microphone stopped recording"</string>
+ <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera and Microphone stopped recording"</string>
<string name="screen_recording_announcement" msgid="2996750593472241520">"Screen recording started"</string>
<string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Screen recording stopped"</string>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fbdccff38731..437d89beaa9e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1422,6 +1422,11 @@
<dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen>
<dimen name="ongoing_call_chip_corner_radius">28dp</dimen>
+ <!-- Status bar user chip -->
+ <dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
+ <dimen name="status_bar_user_chip_end_margin">12dp</dimen>
+ <dimen name="status_bar_user_chip_text_size">12sp</dimen>
+
<!-- Internet panel related dimensions -->
<dimen name="internet_dialog_list_max_height">662dp</dimen>
<!-- The height of the WiFi network in Internet panel. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 72c8163e7517..9eafdb959f07 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1996,6 +1996,9 @@
<!-- SysUI Tuner: Summary of no shortcut being selected [CHAR LIMIT=60] -->
<string name="lockscreen_none">None</string>
+ <!-- ClockId to use when none is set by user -->
+ <string name="lockscreen_clock_id_fallback" translatable="false">DEFAULT</string>
+
<!-- SysUI Tuner: Format string for describing launching an app [CHAR LIMIT=60] -->
<string name="tuner_launch_app">Launch <xliff:g id="app" example="Settings">%1$s</xliff:g></string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ae80070dfa97..fe4f639c307e 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -23,6 +23,12 @@
<item name="android:textColor">@color/status_bar_clock_color</item>
</style>
+ <style name="TextAppearance.StatusBar.UserChip" parent="@*android:style/TextAppearance.StatusBar.Icon">
+ <item name="android:textSize">@dimen/status_bar_user_chip_text_size</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ <item name="android:textColor">@color/status_bar_clock_color</item>
+ </style>
+
<style name="TextAppearance.StatusBar.Expanded" parent="@*android:style/TextAppearance.StatusBar">
<item name="android:textColor">?android:attr/textColorTertiary</item>
</style>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 601cb66d99c2..5c2c27a36e59 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -41,6 +41,7 @@ open class ClockRegistry(
val isEnabled: Boolean,
userHandle: Int,
defaultClockProvider: ClockProvider,
+ val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
) {
// Usually this would be a typealias, but a SAM provides better java interop
fun interface ClockChangeListener {
@@ -69,10 +70,13 @@ open class ClockRegistry(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
)
- ClockSetting.deserialize(json)?.clockId ?: DEFAULT_CLOCK_ID
+ if (json == null || json.isEmpty()) {
+ return fallbackClockId
+ }
+ ClockSetting.deserialize(json).clockId
} catch (ex: Exception) {
Log.e(TAG, "Failed to parse clock setting", ex)
- DEFAULT_CLOCK_ID
+ fallbackClockId
}
}
set(value) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index a0206f1f1e70..819768544b0c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -50,10 +50,9 @@ data class KeyguardFaceListenModel(
override val listening: Boolean,
// keep sorted
val authInterruptActive: Boolean,
- val becauseCannotSkipBouncer: Boolean,
val biometricSettingEnabledForUser: Boolean,
val bouncerFullyShown: Boolean,
- val faceAuthenticated: Boolean,
+ val faceAndFpNotAuthenticated: Boolean,
val faceDisabled: Boolean,
val faceLockedOut: Boolean,
val fpLockedOut: Boolean,
@@ -67,7 +66,9 @@ data class KeyguardFaceListenModel(
val secureCameraLaunched: Boolean,
val switchingUser: Boolean,
val udfpsBouncerShowing: Boolean,
-) : KeyguardListenModel()
+ val udfpsFingerDown: Boolean,
+ val userNotTrustedOrDetectionIsNeeded: Boolean,
+ ) : KeyguardListenModel()
/**
* Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock].
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 2d3dda9b44a5..ce22a81befb5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -794,9 +794,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
// Don't send cancel if authentication succeeds
mFingerprintCancelSignal = null;
+ mLogger.logFingerprintSuccess(userId, isStrongBiometric);
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_FP_AUTHENTICATED);
- mLogger.logFingerprintSuccess(userId, isStrongBiometric);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -2711,9 +2711,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final boolean canBypass = mKeyguardBypassController != null
&& mKeyguardBypassController.canBypass();
// There's no reason to ask the HAL for authentication when the user can dismiss the
- // bouncer, unless we're bypassing and need to auto-dismiss the lock screen even when
- // TrustAgents or biometrics are keeping the device unlocked.
- final boolean becauseCannotSkipBouncer = !getUserCanSkipBouncer(user) || canBypass;
+ // bouncer because the user is trusted, unless we're bypassing and need to auto-dismiss
+ // the lock screen even when TrustAgents are keeping the device unlocked.
+ final boolean userNotTrustedOrDetectionIsNeeded = !getUserHasTrust(user) || canBypass;
// Scan even when encrypted or timeout to show a preemptive bouncer when bypassing.
// Lock-down mode shouldn't scan, since it is more explicit.
@@ -2730,11 +2730,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
strongAuthAllowsScanning = false;
}
- // If the face has recently been authenticated do not attempt to authenticate again.
- final boolean faceAuthenticated = getIsFaceAuthenticated();
+ // If the face or fp has recently been authenticated do not attempt to authenticate again.
+ final boolean faceAndFpNotAuthenticated = !getUserUnlockedWithBiometric(user);
final boolean faceDisabledForUser = isFaceDisabled(user);
final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
+ final boolean isUdfpsFingerDown = mAuthController.isUdfpsFingerDown();
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -2744,13 +2745,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| mOccludingAppRequestingFace
|| awakeKeyguard
|| shouldListenForFaceAssistant
- || mAuthController.isUdfpsFingerDown()
+ || isUdfpsFingerDown
|| mUdfpsBouncerShowing)
- && !mSwitchingUser && !faceDisabledForUser && becauseCannotSkipBouncer
+ && !mSwitchingUser && !faceDisabledForUser && userNotTrustedOrDetectionIsNeeded
&& !mKeyguardGoingAway && biometricEnabledForUser
&& strongAuthAllowsScanning && mIsPrimaryUser
&& (!mSecureCameraLaunched || mOccludingAppRequestingFace)
- && !faceAuthenticated
+ && faceAndFpNotAuthenticated
&& !mGoingToSleep
// We only care about fp locked out state and not face because we still trigger
// face auth even when face is locked out to show the user a message that face
@@ -2764,10 +2765,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
user,
shouldListen,
mAuthInterruptActive,
- becauseCannotSkipBouncer,
biometricEnabledForUser,
mPrimaryBouncerFullyShown,
- faceAuthenticated,
+ faceAndFpNotAuthenticated,
faceDisabledForUser,
isFaceLockedOut(),
fpLockedOut,
@@ -2780,7 +2780,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
strongAuthAllowsScanning,
mSecureCameraLaunched,
mSwitchingUser,
- mUdfpsBouncerShowing));
+ mUdfpsBouncerShowing,
+ isUdfpsFingerDown,
+ userNotTrustedOrDetectionIsNeeded));
return shouldListen;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 9767313331d3..b514f60efc7d 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.os.Handler;
import android.os.UserHandle;
+import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
@@ -50,6 +51,7 @@ public abstract class ClockRegistryModule {
handler,
featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
UserHandle.USER_ALL,
- defaultClockProvider);
+ defaultClockProvider,
+ context.getString(R.string.lockscreen_clock_id_fallback));
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
index 8fc86004c400..a7d4455b43c2 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
@@ -21,10 +21,7 @@ import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherControllerImpl;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
@@ -50,10 +47,4 @@ public abstract class KeyguardStatusBarViewModule {
static StatusBarUserSwitcherContainer getUserSwitcherContainer(KeyguardStatusBarView view) {
return view.findViewById(R.id.user_switcher_container);
}
-
- /** */
- @Binds
- @KeyguardStatusBarViewScope
- abstract StatusBarUserSwitcherController bindStatusBarUserSwitcherController(
- StatusBarUserSwitcherControllerImpl controller);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e27660f7edb0..51691c27f537 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -61,6 +61,9 @@ object Flags {
// TODO(b/254512517): Tracking Bug
val FSI_REQUIRES_KEYGUARD = unreleasedFlag(110, "fsi_requires_keyguard", teamfood = true)
+ // TODO(b/259130119): Tracking Bug
+ val FSI_ON_DND_UPDATE = unreleasedFlag(259130119, "fsi_on_dnd_update", teamfood = true)
+
// TODO(b/254512538): Tracking Bug
val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply", teamfood = true)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 3d976d43759c..dd222c0bf8f8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -137,6 +137,7 @@ import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
@@ -852,6 +853,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
@Override
public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
mOccludeAnimationPlaying = true;
+ mScrimControllerLazy.get().setOccludeAnimationPlaying(true);
}
@Override
@@ -862,6 +864,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// Ensure keyguard state is set correctly if we're cancelled.
mCentralSurfaces.updateIsKeyguard();
+ mScrimControllerLazy.get().setOccludeAnimationPlaying(false);
}
@Override
@@ -875,6 +878,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// Hide the keyguard now that we're done launching the occluding activity over
// it.
mCentralSurfaces.updateIsKeyguard();
+ mScrimControllerLazy.get().setOccludeAnimationPlaying(false);
mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
}
@@ -1132,6 +1136,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private ScreenOnCoordinator mScreenOnCoordinator;
private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+ private Lazy<ScrimController> mScrimControllerLazy;
/**
* Injected constructor. See {@link KeyguardModule}.
@@ -1162,7 +1167,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
DreamOverlayStateController dreamOverlayStateController,
Lazy<ShadeController> shadeControllerLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
- Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
+ Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+ Lazy<ScrimController> scrimControllerLazy) {
mContext = context;
mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
@@ -1207,6 +1213,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mDreamOverlayStateController = dreamOverlayStateController;
mActivityLaunchAnimator = activityLaunchAnimator;
+ mScrimControllerLazy = scrimControllerLazy;
mPowerButtonY = context.getResources().getDimensionPixelSize(
R.dimen.physical_power_button_center_screen_location_y);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index ef3c44340e57..47ef0fac17ab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -54,6 +54,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
@@ -113,7 +114,8 @@ public class KeyguardModule {
DreamOverlayStateController dreamOverlayStateController,
Lazy<ShadeController> shadeController,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
- Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
+ Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+ Lazy<ScrimController> scrimControllerLazy) {
return new KeyguardViewMediator(
context,
userTracker,
@@ -142,7 +144,8 @@ public class KeyguardModule {
dreamOverlayStateController,
shadeController,
notificationShadeWindowController,
- activityLaunchAnimator);
+ activityLaunchAnimator,
+ scrimControllerLazy);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index 9ba3501c3434..03bb7a0f45da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -32,8 +32,6 @@ import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.FgsManagerController
@@ -42,10 +40,9 @@ import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
-import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.security.data.repository.SecurityRepository
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.user.UserSwitcherActivity
+import com.android.systemui.user.domain.interactor.UserInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -100,13 +97,12 @@ class FooterActionsInteractorImpl
@Inject
constructor(
private val activityStarter: ActivityStarter,
- private val featureFlags: FeatureFlags,
private val metricsLogger: MetricsLogger,
private val uiEventLogger: UiEventLogger,
private val deviceProvisionedController: DeviceProvisionedController,
private val qsSecurityFooterUtils: QSSecurityFooterUtils,
private val fgsManagerController: FgsManagerController,
- private val userSwitchDialogController: UserSwitchDialogController,
+ private val userInteractor: UserInteractor,
securityRepository: SecurityRepository,
foregroundServicesRepository: ForegroundServicesRepository,
userSwitcherRepository: UserSwitcherRepository,
@@ -182,22 +178,6 @@ constructor(
}
override fun showUserSwitcher(context: Context, expandable: Expandable) {
- if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
- userSwitchDialogController.showDialog(context, expandable)
- return
- }
-
- val intent =
- Intent(context, UserSwitcherActivity::class.java).apply {
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- }
-
- activityStarter.startActivity(
- intent,
- true /* dismissShade */,
- expandable.activityLaunchController(),
- true /* showOverlockscreenwhenlocked */,
- UserHandle.SYSTEM,
- )
+ userInteractor.showUserSwitcher(context, expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 62f57b8d4068..d8c68780951a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -62,7 +62,8 @@ public class StackScrollAlgorithm {
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
private boolean mIsExpanded;
private boolean mClipNotificationScrollToTop;
- @VisibleForTesting float mHeadsUpInset;
+ @VisibleForTesting
+ float mHeadsUpInset;
private int mPinnedZTranslationExtra;
private float mNotificationScrimPadding;
private int mMarginBottom;
@@ -456,7 +457,7 @@ public class StackScrollAlgorithm {
/**
* @return Fraction to apply to view height and gap between views.
- * Does not include shelf height even if shelf is showing.
+ * Does not include shelf height even if shelf is showing.
*/
protected float getExpansionFractionWithoutShelf(
StackScrollAlgorithmState algorithmState,
@@ -470,7 +471,7 @@ public class StackScrollAlgorithm {
&& (!ambientState.isBypassEnabled() || !ambientState.isPulseExpanding())
? 0 : mNotificationScrimPadding;
- final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding;
+ final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding;
final float stackEndHeight = ambientState.getStackEndHeight() - shelfHeight - scrimPadding;
if (stackEndHeight == 0f) {
// This should not happen, since even when the shade is empty we show EmptyShadeView
@@ -504,13 +505,14 @@ public class StackScrollAlgorithm {
}
// TODO(b/172289889) polish shade open from HUN
+
/**
* Populates the {@link ExpandableViewState} for a single child.
*
- * @param i The index of the child in
- * {@link StackScrollAlgorithmState#visibleChildren}.
- * @param algorithmState The overall output state of the algorithm.
- * @param ambientState The input state provided to the algorithm.
+ * @param i The index of the child in
+ * {@link StackScrollAlgorithmState#visibleChildren}.
+ * @param algorithmState The overall output state of the algorithm.
+ * @param ambientState The input state provided to the algorithm.
*/
protected void updateChild(
int i,
@@ -584,8 +586,8 @@ public class StackScrollAlgorithm {
final float stackBottom = !ambientState.isShadeExpanded()
|| ambientState.getDozeAmount() == 1f
|| bypassPulseNotExpanding
- ? ambientState.getInnerHeight()
- : ambientState.getStackHeight();
+ ? ambientState.getInnerHeight()
+ : ambientState.getStackHeight();
final float shelfStart = stackBottom
- ambientState.getShelf().getIntrinsicHeight()
- mPaddingBetweenElements;
@@ -621,9 +623,9 @@ public class StackScrollAlgorithm {
* Get the gap height needed for before a view
*
* @param sectionProvider the sectionProvider used to understand the sections
- * @param visibleIndex the visible index of this view in the list
- * @param child the child asked about
- * @param previousChild the child right before it or null if none
+ * @param visibleIndex the visible index of this view in the list
+ * @param child the child asked about
+ * @param previousChild the child right before it or null if none
* @return the size of the gap needed or 0 if none is needed
*/
public float getGapHeightForChild(
@@ -657,9 +659,9 @@ public class StackScrollAlgorithm {
* Does a given child need a gap, i.e spacing before a view?
*
* @param sectionProvider the sectionProvider used to understand the sections
- * @param visibleIndex the visible index of this view in the list
- * @param child the child asked about
- * @param previousChild the child right before it or null if none
+ * @param visibleIndex the visible index of this view in the list
+ * @param child the child asked about
+ * @param previousChild the child right before it or null if none
* @return if the child needs a gap height
*/
private boolean childNeedsGapHeight(
@@ -862,30 +864,53 @@ public class StackScrollAlgorithm {
}
}
+ /**
+ * Calculate and update the Z positions for a given child. We currently only give shadows to
+ * HUNs to distinguish a HUN from its surroundings.
+ *
+ * @param isTopHun Whether the child is a top HUN. A top HUN means a HUN that shows on the
+ * vertically top of screen. Top HUNs should have drop shadows
+ * @param childrenOnTop It is greater than 0 when there's an existing HUN that is elevated
+ * @return childrenOnTop The decimal part represents the fraction of the elevated HUN's height
+ * that overlaps with QQS Panel. The integer part represents the count of
+ * previous HUNs whose Z positions are greater than 0.
+ */
protected float updateChildZValue(int i, float childrenOnTop,
StackScrollAlgorithmState algorithmState,
AmbientState ambientState,
- boolean shouldElevateHun) {
+ boolean isTopHun) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
- int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
float baseZ = ambientState.getBaseZHeight();
+
+ // Handles HUN shadow when Shade is opened
+
if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
&& !ambientState.isDozingAndNotPulsing(child)
&& childViewState.getYTranslation() < ambientState.getTopPadding()
+ ambientState.getStackTranslation()) {
+ // Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
+ // Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
+ // When scrolling down shade to make HUN back to in-position in Notification Panel,
+ // The over-lapping fraction goes to 0, and shadows hides gradually.
if (childrenOnTop != 0.0f) {
+ // To elevate the later HUN over previous HUN
childrenOnTop++;
} else {
float overlap = ambientState.getTopPadding()
+ ambientState.getStackTranslation() - childViewState.getYTranslation();
- childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
+ // To prevent over-shadow during HUN entry
+ childrenOnTop += Math.min(
+ 1.0f,
+ overlap / childViewState.height
+ );
+ MathUtils.saturate(childrenOnTop);
}
childViewState.setZTranslation(baseZ
- + childrenOnTop * zDistanceBetweenElements);
- } else if (shouldElevateHun) {
+ + childrenOnTop * mPinnedZTranslationExtra);
+ } else if (isTopHun) {
// In case this is a new view that has never been measured before, we don't want to
- // elevate if we are currently expanded more then the notification
+ // elevate if we are currently expanded more than the notification
int shelfHeight = ambientState.getShelf() == null ? 0 :
ambientState.getShelf().getIntrinsicHeight();
float shelfStart = ambientState.getInnerHeight()
@@ -894,23 +919,28 @@ public class StackScrollAlgorithm {
float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight()
+ mPaddingBetweenElements;
if (shelfStart > notificationEnd) {
+ // When the notification doesn't overlap with Notification Shelf, there's no shadow
childViewState.setZTranslation(baseZ);
} else {
+ // Give shadow to the notification if it overlaps with Notification Shelf
float factor = (notificationEnd - shelfStart) / shelfHeight;
if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
factor = 1.0f;
}
factor = Math.min(factor, 1.0f);
- childViewState.setZTranslation(baseZ + factor * zDistanceBetweenElements);
+ childViewState.setZTranslation(baseZ + factor * mPinnedZTranslationExtra);
}
} else {
childViewState.setZTranslation(baseZ);
}
- // We need to scrim the notification more from its surrounding content when we are pinned,
- // and we therefore elevate it higher.
- // We can use the headerVisibleAmount for this, since the value nicely goes from 0 to 1 when
- // expanding after which we have a normal elevation again.
+ // Handles HUN shadow when shade is closed.
+ // While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays.
+ // During HUN-to-Shade (eg. dragging down HUN to open Shade): headerVisibleAmount goes
+ // gradually from 0 to 1, shadow hides gradually.
+ // Header visibility is a deprecated concept, we are using headerVisibleAmount only because
+ // this value nicely goes from 0 to 1 during the HUN-to-Shade process.
+
childViewState.setZTranslation(childViewState.getZTranslation()
+ (1.0f - child.getHeaderVisibleAmount()) * mPinnedZTranslationExtra);
return childrenOnTop;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 7a49a495155b..13566ef8c630 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -48,6 +48,9 @@ import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
+import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -70,7 +73,7 @@ public class KeyguardStatusBarView extends RelativeLayout {
private ImageView mMultiUserAvatar;
private BatteryMeterView mBatteryView;
private StatusIconContainer mStatusIconContainer;
- private ViewGroup mUserSwitcherContainer;
+ private StatusBarUserSwitcherContainer mUserSwitcherContainer;
private boolean mKeyguardUserSwitcherEnabled;
private boolean mKeyguardUserAvatarEnabled;
@@ -121,8 +124,12 @@ public class KeyguardStatusBarView extends RelativeLayout {
loadDimens();
}
- public ViewGroup getUserSwitcherContainer() {
- return mUserSwitcherContainer;
+ /**
+ * Should only be called from {@link KeyguardStatusBarViewController}
+ * @param viewModel view model for the status bar user chip
+ */
+ void init(StatusBarUserChipViewModel viewModel) {
+ StatusBarUserChipViewBinder.bind(mUserSwitcherContainer, viewModel);
}
@Override
@@ -304,10 +311,7 @@ public class KeyguardStatusBarView extends RelativeLayout {
lp = (LayoutParams) mStatusIconArea.getLayoutParams();
lp.removeRule(RelativeLayout.RIGHT_OF);
lp.width = LayoutParams.WRAP_CONTENT;
-
- LinearLayout.LayoutParams llp =
- (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
- llp.setMarginStart(getResources().getDimensionPixelSize(
+ lp.setMarginStart(getResources().getDimensionPixelSize(
R.dimen.system_icons_super_container_margin_start));
return true;
}
@@ -339,10 +343,7 @@ public class KeyguardStatusBarView extends RelativeLayout {
lp = (LayoutParams) mStatusIconArea.getLayoutParams();
lp.addRule(RelativeLayout.RIGHT_OF, R.id.cutout_space_view);
lp.width = LayoutParams.MATCH_PARENT;
-
- LinearLayout.LayoutParams llp =
- (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
- llp.setMarginStart(0);
+ lp.setMarginStart(0);
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 14cebf4b9f4b..d4dc1dc197a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -59,13 +59,11 @@ import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.fragment.StatusBarIconBlocklistKt;
import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventAnimator;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.SecureSettings;
@@ -110,9 +108,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private final SysuiStatusBarStateController mStatusBarStateController;
private final StatusBarContentInsetsProvider mInsetsProvider;
private final UserManager mUserManager;
- private final StatusBarUserSwitcherFeatureController mFeatureController;
- private final StatusBarUserSwitcherController mUserSwitcherController;
- private final StatusBarUserInfoTracker mStatusBarUserInfoTracker;
+ private final StatusBarUserChipViewModel mStatusBarUserChipViewModel;
private final SecureSettings mSecureSettings;
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
@@ -276,9 +272,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
SysuiStatusBarStateController statusBarStateController,
StatusBarContentInsetsProvider statusBarContentInsetsProvider,
UserManager userManager,
- StatusBarUserSwitcherFeatureController featureController,
- StatusBarUserSwitcherController userSwitcherController,
- StatusBarUserInfoTracker statusBarUserInfoTracker,
+ StatusBarUserChipViewModel userChipViewModel,
SecureSettings secureSettings,
CommandQueue commandQueue,
@Main Executor mainExecutor,
@@ -301,9 +295,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mStatusBarStateController = statusBarStateController;
mInsetsProvider = statusBarContentInsetsProvider;
mUserManager = userManager;
- mFeatureController = featureController;
- mUserSwitcherController = userSwitcherController;
- mStatusBarUserInfoTracker = statusBarUserInfoTracker;
+ mStatusBarUserChipViewModel = userChipViewModel;
mSecureSettings = secureSettings;
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
@@ -328,8 +320,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
R.dimen.header_notifications_collide_distance);
mView.setKeyguardUserAvatarEnabled(
- !mFeatureController.isStatusBarUserSwitcherFeatureEnabled());
- mFeatureController.addCallback(enabled -> mView.setKeyguardUserAvatarEnabled(!enabled));
+ !mStatusBarUserChipViewModel.getChipEnabled());
mSystemEventAnimator = new StatusBarSystemEventAnimator(mView, r);
mDisableStateTracker = new DisableStateTracker(
@@ -344,11 +335,11 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
super.onInit();
mCarrierTextController.init();
mBatteryMeterViewController.init();
- mUserSwitcherController.init();
}
@Override
protected void onViewAttached() {
+ mView.init(mStatusBarUserChipViewModel);
mConfigurationController.addCallback(mConfigurationListener);
mAnimationScheduler.addCallback(mAnimationCallback);
mUserInfoController.addCallback(mOnUserInfoChangedListener);
@@ -394,9 +385,6 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
/** Sets whether user switcher is enabled. */
public void setKeyguardUserSwitcherEnabled(boolean enabled) {
mView.setKeyguardUserSwitcherEnabled(enabled);
- // We don't have a listener for when the user switcher setting changes, so this is
- // where we re-check the state
- mStatusBarUserInfoTracker.checkEnabled();
}
/** Sets whether this controller should listen to battery updates. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 7aeb08dd5ddb..28bc64de2cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -38,6 +38,9 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
+import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.leak.RotationUtils;
import java.util.Objects;
@@ -73,6 +76,11 @@ public class PhoneStatusBarView extends FrameLayout {
mTouchEventHandler = handler;
}
+ void init(StatusBarUserChipViewModel viewModel) {
+ StatusBarUserSwitcherContainer container = findViewById(R.id.user_switcher_container);
+ StatusBarUserChipViewBinder.bind(container, viewModel);
+ }
+
@Override
public void onFinishInflate() {
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index f9c4c8f3b4fe..a6c2b2c2771c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -23,11 +23,11 @@ import android.view.ViewGroup
import android.view.ViewTreeObserver
import com.android.systemui.R
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.UNFOLD_STATUS_BAR
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
import com.android.systemui.util.ViewController
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.view.ViewUtil
@@ -40,7 +40,7 @@ class PhoneStatusBarViewController private constructor(
view: PhoneStatusBarView,
@Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
- private val userSwitcherController: StatusBarUserSwitcherController,
+ private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
touchEventHandler: PhoneStatusBarView.TouchEventHandler,
private val configurationController: ConfigurationController
@@ -91,10 +91,10 @@ class PhoneStatusBarViewController private constructor(
init {
mView.setTouchEventHandler(touchEventHandler)
+ mView.init(userChipViewModel)
}
override fun onInit() {
- userSwitcherController.init()
}
fun setImportantForAccessibility(mode: Int) {
@@ -156,9 +156,9 @@ class PhoneStatusBarViewController private constructor(
private val unfoldComponent: Optional<SysUIUnfoldComponent>,
@Named(UNFOLD_STATUS_BAR)
private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
- private val userSwitcherController: StatusBarUserSwitcherController,
+ private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
- private val configurationController: ConfigurationController
+ private val configurationController: ConfigurationController,
) {
fun create(
view: PhoneStatusBarView,
@@ -168,7 +168,7 @@ class PhoneStatusBarViewController private constructor(
view,
progressProvider.getOrNull(),
unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(),
- userSwitcherController,
+ userChipViewModel,
viewUtil,
touchEventHandler,
configurationController
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 c527f30c424c..fb0d3e4406bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -110,6 +110,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private boolean mClipsQsScrim;
/**
+ * Whether an activity is launching over the lockscreen. During the launch animation, we want to
+ * delay certain scrim changes until after the animation ends.
+ */
+ private boolean mOccludeAnimationPlaying = false;
+
+ /**
* The amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
* shade.
@@ -733,6 +739,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
return mClipsQsScrim;
}
+ public void setOccludeAnimationPlaying(boolean occludeAnimationPlaying) {
+ mOccludeAnimationPlaying = occludeAnimationPlaying;
+ applyAndDispatchState();
+ }
+
private void setOrAdaptCurrentAnimation(@Nullable View scrim) {
if (scrim == null) {
return;
@@ -772,11 +783,15 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING) {
- // Darken scrim as you pull down the shade when unlocked, unless the shade is expanding
- // because we're doing the screen off animation OR the shade is collapsing because
- // we're playing the unlock animation
+ final boolean occluding =
+ mOccludeAnimationPlaying || mState.mLaunchingAffordanceWithPreview;
+
+ // Darken scrim as it's pulled down while unlocked. If we're unlocked but playing the
+ // screen off/occlusion animations, ignore expansion changes while those animations
+ // play.
if (!mScreenOffAnimationController.shouldExpandNotifications()
- && !mAnimatingPanelExpansionOnUnlock) {
+ && !mAnimatingPanelExpansionOnUnlock
+ && !occluding) {
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
if (mClipsQsScrim) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 41f1f9589ce4..efec27099dcd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -29,8 +29,6 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherControllerImpl;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -39,7 +37,6 @@ import java.util.Set;
import javax.inject.Named;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.Multibinds;
@@ -126,12 +123,6 @@ public interface StatusBarFragmentModule {
}
/** */
- @Binds
- @StatusBarFragmentScope
- StatusBarUserSwitcherController bindStatusBarUserSwitcherController(
- StatusBarUserSwitcherControllerImpl controller);
-
- /** */
@Provides
@StatusBarFragmentScope
static PhoneStatusBarViewController providePhoneStatusBarViewController(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
deleted file mode 100644
index f6b8cb0782cd..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
+++ /dev/null
@@ -1,134 +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.statusbar.phone.userswitcher
-
-import android.graphics.drawable.Drawable
-import android.os.UserManager
-import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.policy.CallbackController
-import com.android.systemui.statusbar.policy.UserInfoController
-import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
-import java.io.PrintWriter
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-/**
- * Since every user switcher chip will user the exact same information and logic on whether or not
- * to show, and what data to show, it makes sense to create a single tracker here
- */
-@SysUISingleton
-class StatusBarUserInfoTracker @Inject constructor(
- private val userInfoController: UserInfoController,
- private val userManager: UserManager,
- private val dumpManager: DumpManager,
- @Main private val mainExecutor: Executor,
- @Background private val backgroundExecutor: Executor
-) : CallbackController<CurrentUserChipInfoUpdatedListener>, Dumpable {
- var currentUserName: String? = null
- private set
- var currentUserAvatar: Drawable? = null
- private set
- var userSwitcherEnabled = false
- private set
- private var listening = false
-
- private val listeners = mutableListOf<CurrentUserChipInfoUpdatedListener>()
-
- private val userInfoChangedListener = OnUserInfoChangedListener { name, picture, _ ->
- currentUserAvatar = picture
- currentUserName = name
- notifyListenersUserInfoChanged()
- }
-
- init {
- dumpManager.registerDumpable(TAG, this)
- }
-
- override fun addCallback(listener: CurrentUserChipInfoUpdatedListener) {
- if (listeners.isEmpty()) {
- startListening()
- }
-
- if (!listeners.contains(listener)) {
- listeners.add(listener)
- }
- }
-
- override fun removeCallback(listener: CurrentUserChipInfoUpdatedListener) {
- listeners.remove(listener)
-
- if (listeners.isEmpty()) {
- stopListening()
- }
- }
-
- private fun notifyListenersUserInfoChanged() {
- listeners.forEach {
- it.onCurrentUserChipInfoUpdated()
- }
- }
-
- private fun notifyListenersSettingChanged() {
- listeners.forEach {
- it.onStatusBarUserSwitcherSettingChanged(userSwitcherEnabled)
- }
- }
-
- private fun startListening() {
- listening = true
- userInfoController.addCallback(userInfoChangedListener)
- }
-
- private fun stopListening() {
- listening = false
- userInfoController.removeCallback(userInfoChangedListener)
- }
-
- /**
- * Force a check to [UserManager.isUserSwitcherEnabled], and update listeners if the value has
- * changed
- */
- fun checkEnabled() {
- backgroundExecutor.execute {
- // Check on a background thread to avoid main thread Binder calls
- val wasEnabled = userSwitcherEnabled
- userSwitcherEnabled = userManager.isUserSwitcherEnabled
-
- if (wasEnabled != userSwitcherEnabled) {
- mainExecutor.execute {
- notifyListenersSettingChanged()
- }
- }
- }
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println(" userSwitcherEnabled=$userSwitcherEnabled")
- pw.println(" listening=$listening")
- }
-}
-
-interface CurrentUserChipInfoUpdatedListener {
- fun onCurrentUserChipInfoUpdated()
- fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {}
-}
-
-private const val TAG = "StatusBarUserInfoTracker"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
deleted file mode 100644
index e498ae451400..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
+++ /dev/null
@@ -1,112 +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.statusbar.phone.userswitcher
-
-import android.content.Intent
-import android.os.UserHandle
-import android.view.View
-import com.android.systemui.animation.Expandable
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.UserSwitcherActivity
-import com.android.systemui.util.ViewController
-
-import javax.inject.Inject
-
-/**
- * ViewController for [StatusBarUserSwitcherContainer]
- */
-class StatusBarUserSwitcherControllerImpl @Inject constructor(
- view: StatusBarUserSwitcherContainer,
- private val tracker: StatusBarUserInfoTracker,
- private val featureController: StatusBarUserSwitcherFeatureController,
- private val userSwitcherDialogController: UserSwitchDialogController,
- private val featureFlags: FeatureFlags,
- private val activityStarter: ActivityStarter,
- private val falsingManager: FalsingManager
-) : ViewController<StatusBarUserSwitcherContainer>(view),
- StatusBarUserSwitcherController {
- private val listener = object : CurrentUserChipInfoUpdatedListener {
- override fun onCurrentUserChipInfoUpdated() {
- updateChip()
- }
-
- override fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {
- updateEnabled()
- }
- }
-
- private val featureFlagListener = object : OnUserSwitcherPreferenceChangeListener {
- override fun onUserSwitcherPreferenceChange(enabled: Boolean) {
- updateEnabled()
- }
- }
-
- public override fun onViewAttached() {
- tracker.addCallback(listener)
- featureController.addCallback(featureFlagListener)
- mView.setOnClickListener { view: View ->
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return@setOnClickListener
- }
-
- if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
- val intent = Intent(context, UserSwitcherActivity::class.java)
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
-
- activityStarter.startActivity(intent, true /* dismissShade */,
- null /* ActivityLaunchAnimator.Controller */,
- true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM)
- } else {
- userSwitcherDialogController.showDialog(view.context, Expandable.fromView(view))
- }
- }
-
- updateEnabled()
- }
-
- override fun onViewDetached() {
- tracker.removeCallback(listener)
- featureController.removeCallback(featureFlagListener)
- mView.setOnClickListener(null)
- }
-
- private fun updateChip() {
- mView.text.text = tracker.currentUserName
- mView.avatar.setImageDrawable(tracker.currentUserAvatar)
- }
-
- private fun updateEnabled() {
- if (featureController.isStatusBarUserSwitcherFeatureEnabled() &&
- tracker.userSwitcherEnabled) {
- mView.visibility = View.VISIBLE
- updateChip()
- } else {
- mView.visibility = View.GONE
- }
- }
-}
-
-interface StatusBarUserSwitcherController {
- fun init()
-}
-
-private const val TAG = "SbUserSwitcherController"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
deleted file mode 100644
index 7bae9ff72760..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
+++ /dev/null
@@ -1,63 +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.statusbar.phone.userswitcher
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.policy.CallbackController
-
-import javax.inject.Inject
-
-@SysUISingleton
-class StatusBarUserSwitcherFeatureController @Inject constructor(
- private val flags: FeatureFlags
-) : CallbackController<OnUserSwitcherPreferenceChangeListener> {
- private val listeners = mutableListOf<OnUserSwitcherPreferenceChangeListener>()
-
- init {
- flags.addListener(Flags.STATUS_BAR_USER_SWITCHER) {
- it.requestNoRestart()
- notifyListeners()
- }
- }
-
- fun isStatusBarUserSwitcherFeatureEnabled(): Boolean {
- return flags.isEnabled(Flags.STATUS_BAR_USER_SWITCHER)
- }
-
- override fun addCallback(listener: OnUserSwitcherPreferenceChangeListener) {
- if (!listeners.contains(listener)) {
- listeners.add(listener)
- }
- }
-
- override fun removeCallback(listener: OnUserSwitcherPreferenceChangeListener) {
- listeners.remove(listener)
- }
-
- private fun notifyListeners() {
- val enabled = flags.isEnabled(Flags.STATUS_BAR_USER_SWITCHER)
- listeners.forEach {
- it.onUserSwitcherPreferenceChange(enabled)
- }
- }
-}
-
-interface OnUserSwitcherPreferenceChangeListener {
- fun onUserSwitcherPreferenceChange(enabled: Boolean)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 38b3769c1071..acdf0d2bc32b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -146,7 +146,13 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa
}
private String getDeviceString(CachedBluetoothDevice device) {
- return device.getName() + " " + device.getBondState() + " " + device.isConnected();
+ return device.getName()
+ + " bondState=" + device.getBondState()
+ + " connected=" + device.isConnected()
+ + " active[A2DP]=" + device.isActiveDevice(BluetoothProfile.A2DP)
+ + " active[HEADSET]=" + device.isActiveDevice(BluetoothProfile.HEADSET)
+ + " active[HEARING_AID]=" + device.isActiveDevice(BluetoothProfile.HEARING_AID)
+ + " active[LE_AUDIO]=" + device.isActiveDevice(BluetoothProfile.LE_AUDIO);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 44e5ce865241..fb17b693e17e 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -174,7 +174,7 @@ open class ChipbarCoordinator @Inject constructor(
chipInnerView,
ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_DECELERATE,
- duration = ANIMATION_DURATION,
+ duration = ANIMATION_IN_DURATION,
includeMargins = true,
includeFadeIn = true,
// We can only request focus once the animation finishes.
@@ -187,7 +187,7 @@ open class ChipbarCoordinator @Inject constructor(
view.requireViewById<ViewGroup>(R.id.chipbar_inner),
ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_ACCELERATE,
- ANIMATION_DURATION,
+ ANIMATION_OUT_DURATION,
includeMargins = true,
onAnimationEnd,
)
@@ -208,4 +208,5 @@ open class ChipbarCoordinator @Inject constructor(
}
}
-private const val ANIMATION_DURATION = 500L
+private const val ANIMATION_IN_DURATION = 500L
+private const val ANIMATION_OUT_DURATION = 250L
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index ed53de7dbee7..4c9b8e4639ca 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -23,6 +23,7 @@ import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import androidx.annotation.VisibleForTesting
+import com.android.systemui.R
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -79,6 +80,9 @@ interface UserRepository {
/** Whether we've scheduled the creation of a guest user. */
val isGuestUserCreationScheduled: AtomicBoolean
+ /** Whether to enable the status bar user chip (which launches the user switcher) */
+ val isStatusBarUserChipEnabled: Boolean
+
/** The user of the secondary service. */
var secondaryUserId: Int
@@ -127,6 +131,9 @@ constructor(
override val isGuestUserCreationScheduled = AtomicBoolean()
+ override val isStatusBarUserChipEnabled: Boolean =
+ appContext.resources.getBoolean(R.bool.flag_user_switcher_chip)
+
override var secondaryUserId: Int = UserHandle.USER_NULL
override var isRefreshUsersPaused: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 516c6501c5ff..83f0711caa38 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -34,15 +34,19 @@ import android.util.Log
import com.android.internal.util.UserIcons
import com.android.systemui.R
import com.android.systemui.SystemUISecondaryUserService
+import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.UserSwitcherActivity
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -81,6 +85,7 @@ constructor(
private val repository: UserRepository,
private val activityStarter: ActivityStarter,
private val keyguardInteractor: KeyguardInteractor,
+ private val featureFlags: FeatureFlags,
private val manager: UserManager,
@Application private val applicationScope: CoroutineScope,
telephonyInteractor: TelephonyInteractor,
@@ -256,6 +261,9 @@ constructor(
/** Whether the guest user is currently being reset. */
val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting
+ /** Whether to enable the user chip in the status bar */
+ val isStatusBarUserChipEnabled: Boolean = repository.isStatusBarUserChipEnabled
+
private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null)
val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow()
@@ -468,6 +476,26 @@ constructor(
}
}
+ fun showUserSwitcher(context: Context, expandable: Expandable) {
+ if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+ showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog)
+ return
+ }
+
+ val intent =
+ Intent(context, UserSwitcherActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
+ activityStarter.startActivity(
+ intent,
+ true /* dismissShade */,
+ expandable.activityLaunchController(),
+ true /* showOverlockscreenwhenlocked */,
+ UserHandle.SYSTEM,
+ )
+ }
+
private fun showDialog(request: ShowDialogRequestModel) {
_dialogShowRequests.value = request
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 177356e6b573..85c29647719b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -43,4 +43,7 @@ sealed class ShowDialogRequestModel(
val onExitGuestUser: (guestId: Int, targetId: Int, forceRemoveGuest: Boolean) -> Unit,
override val dialogShower: UserSwitchDialogController.DialogShower?,
) : ShowDialogRequestModel(dialogShower)
+
+ /** Show the user switcher dialog */
+ object ShowUserSwitcherDialog : ShowDialogRequestModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt
new file mode 100644
index 000000000000..8e40f68e27e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.user.ui.binder
+
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@OptIn(InternalCoroutinesApi::class)
+object StatusBarUserChipViewBinder {
+ /** Binds the status bar user chip view model to the given view */
+ @JvmStatic
+ fun bind(
+ view: StatusBarUserSwitcherContainer,
+ viewModel: StatusBarUserChipViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.isChipVisible.collect { isVisible -> view.isVisible = isVisible }
+ }
+
+ launch {
+ viewModel.userName.collect { name -> TextViewBinder.bind(view.text, name) }
+ }
+
+ launch {
+ viewModel.userAvatar.collect { avatar -> view.avatar.setImageDrawable(avatar) }
+ }
+
+ bindButton(view, viewModel)
+ }
+ }
+ }
+
+ private fun bindButton(
+ view: StatusBarUserSwitcherContainer,
+ viewModel: StatusBarUserChipViewModel,
+ ) {
+ view.setOnClickListener { viewModel.onClick(Expandable.fromView(view)) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
new file mode 100644
index 000000000000..ed2589889435
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
@@ -0,0 +1,68 @@
+package com.android.systemui.user.ui.dialog
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.view.LayoutInflater
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.QSUserSwitcherEvent
+import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/**
+ * Extracted from the old UserSwitchDialogController. This is the dialog version of the full-screen
+ * user switcher. See config_enableFullscreenUserSwitcher
+ */
+class UserSwitchDialog(
+ context: Context,
+ adapter: UserDetailView.Adapter,
+ uiEventLogger: UiEventLogger,
+ falsingManager: FalsingManager,
+ activityStarter: ActivityStarter,
+ dialogLaunchAnimator: DialogLaunchAnimator,
+) : SystemUIDialog(context) {
+ init {
+ setShowForAllUsers(true)
+ setCanceledOnTouchOutside(true)
+ setTitle(R.string.qs_user_switch_dialog_title)
+ setPositiveButton(R.string.quick_settings_done) { _, _ ->
+ uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE)
+ }
+ setNeutralButton(
+ R.string.quick_settings_more_user_settings,
+ { _, _ ->
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ uiEventLogger.log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS)
+ val controller =
+ dialogLaunchAnimator.createActivityLaunchController(
+ getButton(BUTTON_NEUTRAL)
+ )
+
+ if (controller == null) {
+ dismiss()
+ }
+
+ activityStarter.postStartActivityDismissingKeyguard(
+ USER_SETTINGS_INTENT,
+ 0,
+ controller
+ )
+ }
+ },
+ false /* dismissOnClick */
+ )
+ val gridFrame =
+ LayoutInflater.from(this.context).inflate(R.layout.qs_user_dialog_content, null)
+ setView(gridFrame)
+
+ adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
+ }
+
+ companion object {
+ private val USER_SETTINGS_INTENT = Intent(Settings.ACTION_USER_SETTINGS)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 58a4473186b3..41410542204c 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -20,6 +20,7 @@ package com.android.systemui.user.ui.dialog
import android.app.Dialog
import android.content.Context
import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.logging.UiEventLogger
import com.android.settingslib.users.UserCreatingDialog
import com.android.systemui.CoreStartable
import com.android.systemui.animation.DialogCuj
@@ -27,11 +28,14 @@ import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.tiles.UserDetailView
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.domain.model.ShowDialogRequestModel
import dagger.Lazy
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
@@ -47,6 +51,9 @@ constructor(
private val broadcastSender: Lazy<BroadcastSender>,
private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
private val interactor: Lazy<UserInteractor>,
+ private val userDetailAdapterProvider: Provider<UserDetailView.Adapter>,
+ private val eventLogger: Lazy<UiEventLogger>,
+ private val activityStarter: Lazy<ActivityStarter>,
) : CoreStartable {
private var currentDialog: Dialog? = null
@@ -108,6 +115,21 @@ constructor(
INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
),
)
+ is ShowDialogRequestModel.ShowUserSwitcherDialog ->
+ Pair(
+ UserSwitchDialog(
+ context = context.get(),
+ adapter = userDetailAdapterProvider.get(),
+ uiEventLogger = eventLogger.get(),
+ falsingManager = falsingManager.get(),
+ activityStarter = activityStarter.get(),
+ dialogLaunchAnimator = dialogLaunchAnimator.get(),
+ ),
+ DialogCuj(
+ InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
+ INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
+ ),
+ )
}
currentDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
new file mode 100644
index 000000000000..3300e8e5b2a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.user.ui.viewmodel
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.domain.interactor.UserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class StatusBarUserChipViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ interactor: UserInteractor,
+) {
+ /** Whether the status bar chip ui should be available */
+ val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled
+
+ /** Whether or not the chip should be showing, based on the number of users */
+ val isChipVisible: Flow<Boolean> =
+ if (!chipEnabled) {
+ flowOf(false)
+ } else {
+ interactor.users.mapLatest { users -> users.size > 1 }
+ }
+
+ /** The display name of the current user */
+ val userName: Flow<Text> = interactor.selectedUser.mapLatest { userModel -> userModel.name }
+
+ /** Avatar for the current user */
+ val userAvatar: Flow<Drawable> =
+ interactor.selectedUser.mapLatest { userModel -> userModel.image }
+
+ /** Action to execute on click. Should launch the user switcher */
+ val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(context, it) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
index 6b5556b3ea91..0f3eddf2eb7c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
@@ -19,7 +19,6 @@ package com.android.systemui.util;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.Context;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -53,8 +52,8 @@ public class DeviceConfigProxy {
/**
* Wrapped version of {@link DeviceConfig#enforceReadPermission}.
*/
- public void enforceReadPermission(Context context, String namespace) {
- DeviceConfig.enforceReadPermission(context, namespace);
+ public void enforceReadPermission(String namespace) {
+ DeviceConfig.enforceReadPermission(namespace);
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 131cf7d33e3a..88396628017b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -84,10 +84,9 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel(
userId = user,
listening = false,
authInterruptActive = false,
- becauseCannotSkipBouncer = false,
biometricSettingEnabledForUser = false,
bouncerFullyShown = false,
- faceAuthenticated = false,
+ faceAndFpNotAuthenticated = false,
faceDisabled = false,
faceLockedOut = false,
fpLockedOut = false,
@@ -101,4 +100,6 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel(
secureCameraLaunched = false,
switchingUser = false,
udfpsBouncerShowing = false,
+ udfpsFingerDown = false,
+ userNotTrustedOrDetectionIsNeeded = false
)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 1ec23e9b542d..7231b3427a71 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -1381,6 +1381,29 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ public void testShouldListenForFace_whenFpIsAlreadyAuthenticated_returnsFalse()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ bouncerFullyVisibleAndNotGoingToSleep();
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ strongAuthNotRequired();
+ biometricsEnabledForCurrentUser();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ userNotCurrentlySwitching();
+
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ successfulFingerprintAuth();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
public void testShouldListenForFace_whenUserIsNotPrimary_returnsFalse() throws RemoteException {
cleanupKeyguardUpdateMonitor();
// This disables face auth
@@ -1934,6 +1957,15 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
.onAuthenticationAcquired(FINGERPRINT_ACQUIRED_START);
}
+ private void successfulFingerprintAuth() {
+ mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
+ .onAuthenticationSucceeded(
+ new FingerprintManager.AuthenticationResult(null,
+ null,
+ mCurrentUserId,
+ true));
+ }
+
private void triggerSuccessfulFaceAuth() {
mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
verify(mFaceManager).authenticate(any(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 45aaaa2418a1..d17e3744edc6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -65,6 +65,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -112,6 +113,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
private @Mock DreamOverlayStateController mDreamOverlayStateController;
private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+ private @Mock ScrimController mScrimController;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -314,7 +316,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mDreamOverlayStateController,
() -> mShadeController,
mNotificationShadeWindowControllerLazy,
- () -> mActivityLaunchAnimator);
+ () -> mActivityLaunchAnimator,
+ () -> mScrimController);
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 8bd71632638d..52b694fac07c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -62,7 +62,6 @@ import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -806,7 +805,6 @@ class MediaDataManagerTest : SysuiTestCase() {
.onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
}
- @Ignore("b/229838140")
@Test
fun testMediaRecommendationDisabled_removesSmartspaceData() {
// GIVEN a media recommendation card is present
@@ -823,7 +821,9 @@ class MediaDataManagerTest : SysuiTestCase() {
tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
// THEN listeners are notified
+ uiExecutor.advanceClockToLast()
foregroundExecutor.advanceClockToLast()
+ uiExecutor.runAllReady()
foregroundExecutor.runAllReady()
verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
index 2c2ddbb9b8c5..645b1cde632f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
@@ -16,10 +16,8 @@
package com.android.systemui.qs.footer.domain.interactor
-import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.os.UserHandle
import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -30,17 +28,13 @@ import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.QSSecurityFooterUtils
import com.android.systemui.qs.footer.FooterActionsTestUtils
-import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.truth.correspondence.FakeUiEvent
import com.android.systemui.truth.correspondence.LogMaker
-import com.android.systemui.user.UserSwitcherActivity
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
@@ -156,54 +150,4 @@ class FooterActionsInteractorTest : SysuiTestCase() {
// We only unlock the device.
verify(activityStarter).postQSRunnableDismissingKeyguard(any())
}
-
- @Test
- fun showUserSwitcher_fullScreenDisabled() {
- val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
- val userSwitchDialogController = mock<UserSwitchDialogController>()
- val underTest =
- utils.footerActionsInteractor(
- featureFlags = featureFlags,
- userSwitchDialogController = userSwitchDialogController,
- )
-
- val expandable = mock<Expandable>()
- underTest.showUserSwitcher(context, expandable)
-
- // Dialog is shown.
- verify(userSwitchDialogController).showDialog(context, expandable)
- }
-
- @Test
- fun showUserSwitcher_fullScreenEnabled() {
- val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
- val activityStarter = mock<ActivityStarter>()
- val underTest =
- utils.footerActionsInteractor(
- featureFlags = featureFlags,
- activityStarter = activityStarter,
- )
-
- // The clicked expandable.
- val expandable = mock<Expandable>()
- underTest.showUserSwitcher(context, expandable)
-
- // Dialog is shown.
- val intentCaptor = argumentCaptor<Intent>()
- verify(activityStarter)
- .startActivity(
- intentCaptor.capture(),
- /* dismissShade= */ eq(true),
- /* ActivityLaunchAnimator.Controller= */ nullable(),
- /* showOverLockscreenWhenLocked= */ eq(true),
- eq(UserHandle.SYSTEM),
- )
- assertThat(intentCaptor.value.component)
- .isEqualTo(
- ComponentName(
- context,
- UserSwitcherActivity::class.java,
- )
- )
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 743e7d69cfc0..4d9db8c28e07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -14,6 +14,7 @@ import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
@@ -31,10 +32,10 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
private val hostView = FrameLayout(context)
private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
- private val notificationRow = mock(ExpandableNotificationRow::class.java)
- private val dumpManager = mock(DumpManager::class.java)
- private val mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager::class.java)
- private val notificationShelf = mock(NotificationShelf::class.java)
+ private val notificationRow = mock<ExpandableNotificationRow>()
+ private val dumpManager = mock<DumpManager>()
+ private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
+ private val notificationShelf = mock<NotificationShelf>()
private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
}
@@ -46,7 +47,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
mStatusBarKeyguardViewManager
)
- private val testableResources = mContext.orCreateTestableResources
+ private val testableResources = mContext.getOrCreateTestableResources()
private fun px(@DimenRes id: Int): Float =
testableResources.resources.getDimensionPixelSize(id).toFloat()
@@ -98,7 +99,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
val marginBottom =
- context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
+ context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY
val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f
assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY)
@@ -507,6 +508,192 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
assertEquals(1f, currentRoundness)
}
+ @Test
+ fun shadeOpened_hunFullyOverlapsQqsPanel_hunShouldHaveFullShadow() {
+ // Given: shade is opened, yTranslation of HUN is 0,
+ // the height of HUN equals to the height of QQS Panel,
+ // and HUN fully overlaps with QQS Panel
+ ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
+ px(R.dimen.qqs_layout_padding_bottom)
+ val childHunView = createHunViewMock(
+ isShadeOpen = true,
+ fullyVisible = false,
+ headerVisibleAmount = 1f
+ )
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: full shadow would be applied
+ assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation)
+ }
+
+ @Test
+ fun shadeOpened_hunPartiallyOverlapsQQS_hunShouldHavePartialShadow() {
+ // Given: shade is opened, yTranslation of HUN is greater than 0,
+ // the height of HUN is equal to the height of QQS Panel,
+ // and HUN partially overlaps with QQS Panel
+ ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
+ px(R.dimen.qqs_layout_padding_bottom)
+ val childHunView = createHunViewMock(
+ isShadeOpen = true,
+ fullyVisible = false,
+ headerVisibleAmount = 1f
+ )
+ // Use half of the HUN's height as overlap
+ childHunView.viewState.yTranslation = (childHunView.viewState.height + 1 shr 1).toFloat()
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: HUN should have shadow, but not as full size
+ assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
+ assertThat(childHunView.viewState.zTranslation)
+ .isLessThan(px(R.dimen.heads_up_pinned_elevation))
+ }
+
+ @Test
+ fun shadeOpened_hunDoesNotOverlapQQS_hunShouldHaveNoShadow() {
+ // Given: shade is opened, yTranslation of HUN is equal to QQS Panel's height,
+ // the height of HUN is equal to the height of QQS Panel,
+ // and HUN doesn't overlap with QQS Panel
+ ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
+ px(R.dimen.qqs_layout_padding_bottom)
+ // Mock the height of shade
+ ambientState.setLayoutMinHeight(1000)
+ val childHunView = createHunViewMock(
+ isShadeOpen = true,
+ fullyVisible = true,
+ headerVisibleAmount = 1f
+ )
+ // HUN doesn't overlap with QQS Panel
+ childHunView.viewState.yTranslation = ambientState.topPadding +
+ ambientState.stackTranslation
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: HUN should not have shadow
+ assertEquals(0f, childHunView.viewState.zTranslation)
+ }
+
+ @Test
+ fun shadeClosed_hunShouldHaveFullShadow() {
+ // Given: shade is closed, ambientState.stackTranslation == -ambientState.topPadding,
+ // the height of HUN is equal to the height of QQS Panel,
+ ambientState.stackTranslation = -ambientState.topPadding
+ // Mock the height of shade
+ ambientState.setLayoutMinHeight(1000)
+ val childHunView = createHunViewMock(
+ isShadeOpen = false,
+ fullyVisible = false,
+ headerVisibleAmount = 0f
+ )
+ childHunView.viewState.yTranslation = 0f
+ // Shade is closed, thus childHunView's headerVisibleAmount is 0
+ childHunView.headerVisibleAmount = 0f
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: HUN should have full shadow
+ assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation)
+ }
+
+ @Test
+ fun draggingHunToOpenShade_hunShouldHavePartialShadow() {
+ // Given: shade is closed when HUN pops up,
+ // now drags down the HUN to open shade
+ ambientState.stackTranslation = -ambientState.topPadding
+ // Mock the height of shade
+ ambientState.setLayoutMinHeight(1000)
+ val childHunView = createHunViewMock(
+ isShadeOpen = false,
+ fullyVisible = false,
+ headerVisibleAmount = 0.5f
+ )
+ childHunView.viewState.yTranslation = 0f
+ // Shade is being opened, thus childHunView's headerVisibleAmount is between 0 and 1
+ // use 0.5 as headerVisibleAmount here
+ childHunView.headerVisibleAmount = 0.5f
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: HUN should have shadow, but not as full size
+ assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
+ assertThat(childHunView.viewState.zTranslation)
+ .isLessThan(px(R.dimen.heads_up_pinned_elevation))
+ }
+
+ private fun createHunViewMock(
+ isShadeOpen: Boolean,
+ fullyVisible: Boolean,
+ headerVisibleAmount: Float
+ ) =
+ mock<ExpandableNotificationRow>().apply {
+ val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible)
+ whenever(this.viewState).thenReturn(childViewStateMock)
+
+ whenever(this.mustStayOnScreen()).thenReturn(true)
+ whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount)
+ }
+
+
+ private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) =
+ ExpandableViewState().apply {
+ // Mock the HUN's height with ambientState.topPadding +
+ // ambientState.stackTranslation
+ height = (ambientState.topPadding + ambientState.stackTranslation).toInt()
+ if (isShadeOpen && fullyVisible) {
+ yTranslation =
+ ambientState.topPadding + ambientState.stackTranslation
+ } else {
+ yTranslation = 0f
+ }
+ headsUpIsVisible = fullyVisible
+ }
+
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction: Float,
expectedAlpha: Float
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 6ec5cf82a81e..eb0b9b3a3fb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -56,14 +56,12 @@ import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -112,16 +110,12 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider;
@Mock
private UserManager mUserManager;
+ @Mock
+ private StatusBarUserChipViewModel mStatusBarUserChipViewModel;
@Captor
private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
- @Mock
- private StatusBarUserSwitcherFeatureController mStatusBarUserSwitcherFeatureController;
- @Mock
- private StatusBarUserSwitcherController mStatusBarUserSwitcherController;
- @Mock
- private StatusBarUserInfoTracker mStatusBarUserInfoTracker;
@Mock private SecureSettings mSecureSettings;
@Mock private CommandQueue mCommandQueue;
@Mock private KeyguardLogger mLogger;
@@ -169,9 +163,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
mStatusBarStateController,
mStatusBarContentInsetsProvider,
mUserManager,
- mStatusBarUserSwitcherFeatureController,
- mStatusBarUserSwitcherController,
- mStatusBarUserInfoTracker,
+ mStatusBarUserChipViewModel,
mSecureSettings,
mCommandQueue,
mFakeExecutor,
@@ -479,8 +471,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
@Test
public void testNewUserSwitcherDisablesAvatar_newUiOn() {
// GIVEN the status bar user switcher chip is enabled
- when(mStatusBarUserSwitcherFeatureController.isStatusBarUserSwitcherFeatureEnabled())
- .thenReturn(true);
+ when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(true);
// WHEN the controller is created
mController = createController();
@@ -492,8 +483,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
@Test
public void testNewUserSwitcherDisablesAvatar_newUiOff() {
// GIVEN the status bar user switcher chip is disabled
- when(mStatusBarUserSwitcherFeatureController.isStatusBarUserSwitcherFeatureEnabled())
- .thenReturn(false);
+ when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(false);
// WHEN the controller is created
mController = createController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index a61fba5c4000..320a08315843 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -27,11 +27,11 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.shade.NotificationPanelViewController
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
import com.android.systemui.util.mockito.any
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
@@ -64,7 +64,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var configurationController: ConfigurationController
@Mock
- private lateinit var userSwitcherController: StatusBarUserSwitcherController
+ private lateinit var userChipViewModel: StatusBarUserChipViewModel
@Mock
private lateinit var viewUtil: ViewUtil
@@ -79,14 +79,13 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
`when`(notificationPanelViewController.view).thenReturn(panelView)
`when`(sysuiUnfoldComponent.getStatusBarMoveFromCenterAnimationController())
.thenReturn(moveFromCenterAnimation)
- // create the view on main thread as it requires main looper
+ // create the view and controller on main thread as it requires main looper
InstrumentationRegistry.getInstrumentation().runOnMainSync {
val parent = FrameLayout(mContext) // add parent to keep layout params
view = LayoutInflater.from(mContext)
.inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
+ controller = createAndInitController(view)
}
-
- controller = createAndInitController(view)
}
@Test
@@ -106,7 +105,10 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
val view = createViewMock()
val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
unfoldConfig.isEnabled = true
- controller = createAndInitController(view)
+ // create the controller on main thread as it requires main looper
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ controller = createAndInitController(view)
+ }
verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
argumentCaptor.value.onPreDraw()
@@ -126,7 +128,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
return PhoneStatusBarViewController.Factory(
Optional.of(sysuiUnfoldComponent),
Optional.of(progressProvider),
- userSwitcherController,
+ userChipViewModel,
viewUtil,
configurationController
).create(view, touchEventHandler).also {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
deleted file mode 100644
index eba3b04f3472..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
+++ /dev/null
@@ -1,102 +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.statusbar.phone.userswitcher
-
-import android.content.Intent
-import android.os.UserHandle
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-@SmallTest
-class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() {
- @Mock
- private lateinit var tracker: StatusBarUserInfoTracker
-
- @Mock
- private lateinit var featureController: StatusBarUserSwitcherFeatureController
-
- @Mock
- private lateinit var userSwitcherDialogController: UserSwitchDialogController
-
- @Mock
- private lateinit var featureFlags: FeatureFlags
-
- @Mock
- private lateinit var activityStarter: ActivityStarter
-
- @Mock
- private lateinit var falsingManager: FalsingManager
-
- private lateinit var statusBarUserSwitcherContainer: StatusBarUserSwitcherContainer
- private lateinit var controller: StatusBarUserSwitcherControllerImpl
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- statusBarUserSwitcherContainer = StatusBarUserSwitcherContainer(mContext, null)
- statusBarUserSwitcherContainer
- controller = StatusBarUserSwitcherControllerImpl(
- statusBarUserSwitcherContainer,
- tracker,
- featureController,
- userSwitcherDialogController,
- featureFlags,
- activityStarter,
- falsingManager
- )
- controller.init()
- controller.onViewAttached()
- }
-
- @Test
- fun testFalsingManager() {
- statusBarUserSwitcherContainer.callOnClick()
- verify(falsingManager).isFalseTap(FalsingManager.LOW_PENALTY)
- }
-
- @Test
- fun testStartActivity() {
- `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(false)
- statusBarUserSwitcherContainer.callOnClick()
- verify(userSwitcherDialogController).showDialog(any(), any())
- `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(true)
- statusBarUserSwitcherContainer.callOnClick()
- verify(activityStarter).startActivity(any(Intent::class.java),
- eq(true) /* dismissShade */,
- eq(null) /* animationController */,
- eq(true) /* showOverLockscreenWhenLocked */,
- eq(UserHandle.SYSTEM))
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 463f517d5063..4b49420c99be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.user.domain.interactor
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
+import android.content.ComponentName
import android.content.Intent
import android.content.pm.UserInfo
import android.graphics.Bitmap
@@ -33,7 +34,10 @@ import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
@@ -41,6 +45,7 @@ import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.UserSwitcherActivity
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -48,9 +53,11 @@ import com.android.systemui.user.domain.model.ShowDialogRequestModel
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
@@ -90,6 +97,7 @@ class UserInteractorTest : SysuiTestCase() {
private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var telephonyRepository: FakeTelephonyRepository
+ private lateinit var featureFlags: FakeFeatureFlags
@Before
fun setUp() {
@@ -104,6 +112,7 @@ class UserInteractorTest : SysuiTestCase() {
SUPERVISED_USER_CREATION_APP_PACKAGE,
)
+ featureFlags = FakeFeatureFlags()
userRepository = FakeUserRepository()
keyguardRepository = FakeKeyguardRepository()
telephonyRepository = FakeTelephonyRepository()
@@ -147,7 +156,8 @@ class UserInteractorTest : SysuiTestCase() {
uiEventLogger = uiEventLogger,
resumeSessionReceiver = resumeSessionReceiver,
resetOrExitSessionReceiver = resetOrExitSessionReceiver,
- )
+ ),
+ featureFlags = featureFlags,
)
}
@@ -715,6 +725,52 @@ class UserInteractorTest : SysuiTestCase() {
job.cancel()
}
+ @Test
+ fun `show user switcher - full screen disabled - shows dialog switcher`() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+
+ var dialogRequest: ShowDialogRequestModel? = null
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(context, expandable)
+
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ // Dialog is shown.
+ assertThat(dialogRequest).isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog)
+
+ underTest.onDialogShown()
+ assertThat(dialogRequest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `show user switcher - full screen enabled - launches activity`() {
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(context, expandable)
+
+ // Dialog is shown.
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(activityStarter)
+ .startActivity(
+ intentCaptor.capture(),
+ /* dismissShade= */ eq(true),
+ /* ActivityLaunchAnimator.Controller= */ nullable(),
+ /* showOverLockscreenWhenLocked= */ eq(true),
+ eq(UserHandle.SYSTEM),
+ )
+ assertThat(intentCaptor.value.component)
+ .isEqualTo(
+ ComponentName(
+ context,
+ UserSwitcherActivity::class.java,
+ )
+ )
+ }
+
private fun assertUsers(
models: List<UserModel>?,
count: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
new file mode 100644
index 000000000000..db348b8029a0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -0,0 +1,306 @@
+/*
+ * 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.user.ui.viewmodel
+
+import android.app.ActivityManager
+import android.app.admin.DevicePolicyManager
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.doAnswer
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class StatusBarUserChipViewModelTest : SysuiTestCase() {
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var activityManager: ActivityManager
+ @Mock private lateinit var manager: UserManager
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+ @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
+
+ private lateinit var underTest: StatusBarUserChipViewModel
+
+ private val userRepository = FakeUserRepository()
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val featureFlags = FakeFeatureFlags()
+ private lateinit var guestUserInteractor: GuestUserInteractor
+ private lateinit var refreshUsersScheduler: RefreshUsersScheduler
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ doAnswer { invocation ->
+ val userId = invocation.arguments[0] as Int
+ when (userId) {
+ USER_ID_0 -> return@doAnswer USER_IMAGE_0
+ USER_ID_1 -> return@doAnswer USER_IMAGE_1
+ USER_ID_2 -> return@doAnswer USER_IMAGE_2
+ else -> return@doAnswer mock<Bitmap>()
+ }
+ }
+ .`when`(manager)
+ .getUserIcon(anyInt())
+
+ userRepository.isStatusBarUserChipEnabled = true
+
+ refreshUsersScheduler =
+ RefreshUsersScheduler(
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
+ repository = userRepository,
+ )
+ guestUserInteractor =
+ GuestUserInteractor(
+ applicationContext = context,
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
+ backgroundDispatcher = testDispatcher,
+ manager = manager,
+ repository = userRepository,
+ deviceProvisionedController = deviceProvisionedController,
+ devicePolicyManager = devicePolicyManager,
+ refreshUsersScheduler = refreshUsersScheduler,
+ uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
+ )
+
+ underTest = viewModel()
+ }
+
+ @Test
+ fun `config is false - chip is disabled`() {
+ // the enabled bit is set at SystemUI startup, so recreate the view model here
+ userRepository.isStatusBarUserChipEnabled = false
+ underTest = viewModel()
+
+ assertThat(underTest.chipEnabled).isFalse()
+ }
+
+ @Test
+ fun `config is true - chip is enabled`() {
+ // the enabled bit is set at SystemUI startup, so recreate the view model here
+ userRepository.isStatusBarUserChipEnabled = true
+ underTest = viewModel()
+
+ assertThat(underTest.chipEnabled).isTrue()
+ }
+
+ @Test
+ fun `should show chip criteria - single user`() =
+ testScope.runTest {
+ userRepository.setUserInfos(listOf(USER_0))
+ userRepository.setSelectedUserInfo(USER_0)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ val values = mutableListOf<Boolean>()
+
+ val job = launch { underTest.isChipVisible.toList(values) }
+ advanceUntilIdle()
+
+ assertThat(values).containsExactly(false)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `should show chip criteria - multiple users`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ var latest: Boolean? = null
+ val job = underTest.isChipVisible.onEach { latest = it }.launchIn(this)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `user chip name - shows selected user info`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ var latest: Text? = null
+ val job = underTest.userName.onEach { latest = it }.launchIn(this)
+
+ userRepository.setSelectedUserInfo(USER_0)
+ assertThat(latest).isEqualTo(USER_NAME_0)
+
+ userRepository.setSelectedUserInfo(USER_1)
+ assertThat(latest).isEqualTo(USER_NAME_1)
+
+ userRepository.setSelectedUserInfo(USER_2)
+ assertThat(latest).isEqualTo(USER_NAME_2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `user chip avatar - shows selected user info`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ // A little hacky. System server passes us bitmaps and we wrap them in the interactor.
+ // Unwrap them to make sure we're always tracking the current user's bitmap
+ var latest: Bitmap? = null
+ val job =
+ underTest.userAvatar
+ .onEach {
+ if (it !is BitmapDrawable) {
+ latest = null
+ }
+
+ latest = (it as BitmapDrawable).bitmap
+ }
+ .launchIn(this)
+
+ userRepository.setSelectedUserInfo(USER_0)
+ assertThat(latest).isEqualTo(USER_IMAGE_0)
+
+ userRepository.setSelectedUserInfo(USER_1)
+ assertThat(latest).isEqualTo(USER_IMAGE_1)
+
+ userRepository.setSelectedUserInfo(USER_2)
+ assertThat(latest).isEqualTo(USER_IMAGE_2)
+
+ job.cancel()
+ }
+
+ private fun viewModel(): StatusBarUserChipViewModel {
+ return StatusBarUserChipViewModel(
+ context = context,
+ interactor =
+ UserInteractor(
+ applicationContext = context,
+ repository = userRepository,
+ activityStarter = activityStarter,
+ keyguardInteractor =
+ KeyguardInteractor(
+ repository = keyguardRepository,
+ ),
+ featureFlags = featureFlags,
+ manager = manager,
+ applicationScope = testScope.backgroundScope,
+ telephonyInteractor =
+ TelephonyInteractor(
+ repository = FakeTelephonyRepository(),
+ ),
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ backgroundDispatcher = testDispatcher,
+ activityManager = activityManager,
+ refreshUsersScheduler = refreshUsersScheduler,
+ guestUserInteractor = guestUserInteractor,
+ )
+ )
+ }
+
+ private suspend fun setMultipleUsers() {
+ userRepository.setUserInfos(listOf(USER_0, USER_1, USER_2))
+ userRepository.setSelectedUserInfo(USER_0)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ }
+
+ companion object {
+ private const val USER_ID_0 = 0
+ private val USER_IMAGE_0 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_0 = Text.Loaded("zero")
+
+ private const val USER_ID_1 = 1
+ private val USER_IMAGE_1 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_1 = Text.Loaded("one")
+
+ private const val USER_ID_2 = 2
+ private val USER_IMAGE_2 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_2 = Text.Loaded("two")
+
+ private val USER_0 =
+ UserInfo(
+ USER_ID_0,
+ USER_NAME_0.text!!,
+ /* iconPath */ "",
+ /* flags */ 0,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+
+ private val USER_1 =
+ UserInfo(
+ USER_ID_1,
+ USER_NAME_1.text!!,
+ /* iconPath */ "",
+ /* flags */ 0,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+
+ private val USER_2 =
+ UserInfo(
+ USER_ID_2,
+ USER_NAME_2.text!!,
+ /* iconPath */ "",
+ /* flags */ 0,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index db136800a3cc..eac7fc21e505 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -27,6 +27,7 @@ import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
@@ -147,6 +148,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
KeyguardInteractor(
repository = keyguardRepository,
),
+ featureFlags = FakeFeatureFlags(),
manager = manager,
applicationScope = injectedScope,
telephonyInteractor =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 325da4ead666..63448e236867 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -28,8 +28,6 @@ import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -43,7 +41,6 @@ import com.android.systemui.qs.footer.data.repository.UserSwitcherRepositoryImpl
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.security.data.repository.SecurityRepository
import com.android.systemui.security.data.repository.SecurityRepositoryImpl
import com.android.systemui.settings.FakeUserTracker
@@ -54,6 +51,7 @@ import com.android.systemui.statusbar.policy.FakeUserInfoController
import com.android.systemui.statusbar.policy.SecurityController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.GlobalSettings
@@ -97,13 +95,12 @@ class FooterActionsTestUtils(
/** Create a [FooterActionsInteractor] to be used in tests. */
fun footerActionsInteractor(
activityStarter: ActivityStarter = mock(),
- featureFlags: FeatureFlags = FakeFeatureFlags(),
metricsLogger: MetricsLogger = FakeMetricsLogger(),
uiEventLogger: UiEventLogger = UiEventLoggerFake(),
deviceProvisionedController: DeviceProvisionedController = mock(),
qsSecurityFooterUtils: QSSecurityFooterUtils = mock(),
fgsManagerController: FgsManagerController = mock(),
- userSwitchDialogController: UserSwitchDialogController = mock(),
+ userInteractor: UserInteractor = mock(),
securityRepository: SecurityRepository = securityRepository(),
foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(),
userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(),
@@ -112,13 +109,12 @@ class FooterActionsTestUtils(
): FooterActionsInteractor {
return FooterActionsInteractorImpl(
activityStarter,
- featureFlags,
metricsLogger,
uiEventLogger,
deviceProvisionedController,
qsSecurityFooterUtils,
fgsManagerController,
- userSwitchDialogController,
+ userInteractor,
securityRepository,
foregroundServicesRepository,
userSwitcherRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index b7c8cbf40bea..ea5a302ce05a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -49,6 +49,8 @@ class FakeUserRepository : UserRepository {
override val isGuestUserCreationScheduled = AtomicBoolean()
+ override var isStatusBarUserChipEnabled: Boolean = false
+
override var secondaryUserId: Int = UserHandle.USER_NULL
override var isRefreshUsersPaused: Boolean = false
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
index 33ece0084906..21e16a1e7be4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
@@ -16,7 +16,6 @@
package com.android.systemui.util;
-import android.content.Context;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.OnPropertiesChangedListener;
import android.provider.DeviceConfig.Properties;
@@ -83,7 +82,7 @@ public class DeviceConfigProxyFake extends DeviceConfigProxy {
}
@Override
- public void enforceReadPermission(Context context, String namespace) {
+ public void enforceReadPermission(String namespace) {
// no-op
}
diff --git a/services/Android.bp b/services/Android.bp
index 76a148419506..f6570e9d2702 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -60,9 +60,17 @@ system_optimized_java_defaults {
ignore_warnings: false,
proguard_flags_files: ["proguard.flags"],
},
- // Note: Optimizations are disabled by default if unspecified in
- // the java_library rule.
- conditions_default: {},
+ conditions_default: {
+ optimize: {
+ enabled: true,
+ optimize: false,
+ shrink: true,
+ ignore_warnings: false,
+ // Note that this proguard config is very conservative, only shrinking the
+ // permission subpackage to prune unused jarjar'ed Kotlin dependencies.
+ proguard_flags_files: ["proguard_permission.flags"],
+ },
+ },
},
},
}
@@ -97,6 +105,7 @@ filegroup {
":services.midi-sources",
":services.musicsearch-sources",
":services.net-sources",
+ ":services.permission-sources",
":services.print-sources",
":services.profcollect-sources",
":services.restrictions-sources",
@@ -131,6 +140,7 @@ java_library {
app_image: true,
profile: "art-profile",
},
+ exclude_kotlinc_generated_files: true,
srcs: [":services-main-sources"],
@@ -152,6 +162,7 @@ java_library {
"services.musicsearch",
"services.net",
"services.people",
+ "services.permission",
"services.print",
"services.profcollect",
"services.restrictions",
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9ea9e34f58a1..39f6ef2290f8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -181,6 +181,7 @@ import android.app.ApplicationErrorReport;
import android.app.ApplicationExitInfo;
import android.app.ApplicationThreadConstants;
import android.app.BroadcastOptions;
+import android.app.ComponentOptions;
import android.app.ContentProviderHolder;
import android.app.IActivityController;
import android.app.IActivityManager;
@@ -13914,10 +13915,10 @@ public class ActivityManagerService extends IActivityManager.Stub
throw new SecurityException(
"Non-system callers may not flag broadcasts as alarm");
}
- if (options.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) {
+ if (options.containsKey(ComponentOptions.KEY_INTERACTIVE)) {
enforceCallingPermission(
- android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
- "setInteractiveBroadcast");
+ android.Manifest.permission.COMPONENT_OPTION_INTERACTIVE,
+ "setInteractive");
}
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 6ea2dee5b578..84d744270e82 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -397,7 +397,7 @@ final class BroadcastRecord extends Binder {
alarm = options != null && options.isAlarmBroadcast();
pushMessage = options != null && options.isPushMessagingBroadcast();
pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
- interactive = options != null && options.isInteractiveBroadcast();
+ interactive = options != null && options.isInteractive();
this.filterExtrasForReceiver = filterExtrasForReceiver;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 9bce471fd0cb..8a22ab968165 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -837,7 +837,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
void enableAudioReturnChannel(boolean enabled) {
assertRunOnServiceThread();
HdmiDeviceInfo avr = getAvrDeviceInfo();
- if (avr != null) {
+ if (avr != null && avr.getPortId() != Constants.INVALID_PORT_ID) {
mService.enableAudioReturnChannel(avr.getPortId(), enabled);
}
}
@@ -1336,19 +1336,31 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
@ServiceThreadOnly
+ private void forceDisableArcOnAllPins() {
+ List<HdmiPortInfo> ports = mService.getPortInfo();
+ for (HdmiPortInfo port : ports) {
+ if (isArcFeatureEnabled(port.getId())) {
+ mService.enableAudioReturnChannel(port.getId(), false);
+ }
+ }
+ }
+
+ @ServiceThreadOnly
private void disableArcIfExist() {
assertRunOnServiceThread();
HdmiDeviceInfo avr = getAvrDeviceInfo();
if (avr == null) {
return;
}
- disableArc();
// Seq #44.
removeAllRunningArcAction();
if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) {
addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
}
+
+ // Disable ARC Pin earlier, prevent the case where AVR doesn't send <Terminate ARC> in time
+ forceDisableArcOnAllPins();
}
@ServiceThreadOnly
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index c97711b3aa80..5b837f1daa6f 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -81,11 +81,8 @@ import com.android.server.utils.Watcher;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Objects;
-import java.util.Set;
/**
* Implementation of the methods that update the internal structures of AppsFilter. Because of the
@@ -113,7 +110,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
*/
@GuardedBy("mQueryableViaUsesPermissionLock")
@NonNull
- private HashMap<String, Set<Integer>> mPermissionToUids;
+ private final ArrayMap<String, ArraySet<Integer>> mPermissionToUids;
/**
* A cache that maps parsed {@link android.R.styleable#AndroidManifestUsesPermission
@@ -123,7 +120,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
*/
@GuardedBy("mQueryableViaUsesPermissionLock")
@NonNull
- private HashMap<String, Set<Integer>> mUsesPermissionToUids;
+ private final ArrayMap<String, ArraySet<Integer>> mUsesPermissionToUids;
/**
* Ensures an observer is in the list, exactly once. The observer cannot be null. The
@@ -225,8 +222,8 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
mProtectedBroadcasts = new WatchedArraySet<>();
mProtectedBroadcastsSnapshot = new SnapshotCache.Auto<>(
mProtectedBroadcasts, mProtectedBroadcasts, "AppsFilter.mProtectedBroadcasts");
- mPermissionToUids = new HashMap<>();
- mUsesPermissionToUids = new HashMap<>();
+ mPermissionToUids = new ArrayMap<>();
+ mUsesPermissionToUids = new ArrayMap<>();
mSnapshot = new SnapshotCache<AppsFilterSnapshot>(this, this) {
@Override
@@ -609,7 +606,10 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
// Lookup in the mPermissionToUids cache if installed packages have
// defined this permission.
if (mPermissionToUids.containsKey(usesPermissionName)) {
- for (int targetAppId : mPermissionToUids.get(usesPermissionName)) {
+ final ArraySet<Integer> permissionDefiners =
+ mPermissionToUids.get(usesPermissionName);
+ for (int j = 0; j < permissionDefiners.size(); j++) {
+ final int targetAppId = permissionDefiners.valueAt(j);
if (targetAppId != newPkgSetting.getAppId()) {
mQueryableViaUsesPermission.add(newPkgSetting.getAppId(),
targetAppId);
@@ -619,7 +619,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
// Record in mUsesPermissionToUids that a permission was requested
// by a new package
if (!mUsesPermissionToUids.containsKey(usesPermissionName)) {
- mUsesPermissionToUids.put(usesPermissionName, new HashSet<>());
+ mUsesPermissionToUids.put(usesPermissionName, new ArraySet<>());
}
mUsesPermissionToUids.get(usesPermissionName).add(newPkgSetting.getAppId());
}
@@ -633,7 +633,10 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
// Lookup in the mUsesPermissionToUids cache if installed packages have
// requested this permission.
if (mUsesPermissionToUids.containsKey(permissionName)) {
- for (int queryingAppId : mUsesPermissionToUids.get(permissionName)) {
+ final ArraySet<Integer> permissionUsers = mUsesPermissionToUids.get(
+ permissionName);
+ for (int j = 0; j < permissionUsers.size(); j++) {
+ final int queryingAppId = permissionUsers.valueAt(j);
if (queryingAppId != newPkgSetting.getAppId()) {
mQueryableViaUsesPermission.add(queryingAppId,
newPkgSetting.getAppId());
@@ -642,7 +645,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
}
// Record in mPermissionToUids that a permission was defined by a new package
if (!mPermissionToUids.containsKey(permissionName)) {
- mPermissionToUids.put(permissionName, new HashSet<>());
+ mPermissionToUids.put(permissionName, new ArraySet<>());
}
mPermissionToUids.get(permissionName).add(newPkgSetting.getAppId());
}
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 01a8bd0a4225..4e5a6f9fa2b5 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED;
import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
@@ -114,6 +115,8 @@ final class InstallRequest {
@Nullable
private final PackageMetrics mPackageMetrics;
+ private final int mSessionId;
+ private final int mRequireUserAction;
// New install
InstallRequest(InstallingSession params) {
@@ -128,6 +131,8 @@ final class InstallRequest {
params.mDataLoaderType, params.mPackageSource);
mPackageMetrics = new PackageMetrics(this);
mIsInstallInherit = params.mIsInherit;
+ mSessionId = params.mSessionId;
+ mRequireUserAction = params.mRequireUserAction;
}
// Install existing package as user
@@ -141,6 +146,8 @@ final class InstallRequest {
mPostInstallRunnable = runnable;
mPackageMetrics = new PackageMetrics(this);
mIsInstallForUsers = true;
+ mSessionId = -1;
+ mRequireUserAction = USER_ACTION_UNSPECIFIED;
}
// addForInit
@@ -158,6 +165,8 @@ final class InstallRequest {
mScanFlags = scanFlags;
mScanResult = scanResult;
mPackageMetrics = null; // No logging from this code path
+ mSessionId = -1;
+ mRequireUserAction = USER_ACTION_UNSPECIFIED;
}
@Nullable
@@ -565,6 +574,14 @@ final class InstallRequest {
}
}
+ public int getSessionId() {
+ return mSessionId;
+ }
+
+ public int getRequireUserAction() {
+ return mRequireUserAction;
+ }
+
public void setScanFlags(int scanFlags) {
mScanFlags = scanFlags;
}
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index e4a0a3ab8dfa..d8494dbaa827 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.content.pm.PackageInstaller.SessionParams.MODE_INHERIT_EXISTING;
+import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED;
import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
@@ -95,7 +96,10 @@ class InstallingSession {
final InstallPackageHelper mInstallPackageHelper;
final RemovePackageHelper mRemovePackageHelper;
final boolean mIsInherit;
+ final int mSessionId;
+ final int mRequireUserAction;
+ // For move install
InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
int installFlags, InstallSource installSource, String volumeUuid,
UserHandle user, String packageAbiOverride, int packageSource,
@@ -124,9 +128,11 @@ class InstallingSession {
mPackageSource = packageSource;
mPackageLite = packageLite;
mIsInherit = false;
+ mSessionId = -1;
+ mRequireUserAction = USER_ACTION_UNSPECIFIED;
}
- InstallingSession(File stagedDir, IPackageInstallObserver2 observer,
+ InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
PackageInstaller.SessionParams sessionParams, InstallSource installSource,
UserHandle user, SigningDetails signingDetails, int installerUid,
PackageLite packageLite, PackageManagerService pm) {
@@ -155,6 +161,8 @@ class InstallingSession {
mPackageSource = sessionParams.packageSource;
mPackageLite = packageLite;
mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING;
+ mSessionId = sessionId;
+ mRequireUserAction = sessionParams.requireUserAction;
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5d13a45e03e3..a2b462a8d76c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2566,8 +2566,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
synchronized (mLock) {
- return new InstallingSession(stageDir, localObserver, params, mInstallSource, user,
- mSigningDetails, mInstallerUid, mPackageLite, mPm);
+ return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource,
+ user, mSigningDetails, mInstallerUid, mPackageLite, mPm);
}
}
@@ -3711,7 +3711,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private boolean dispatchPendingAbandonCallback() {
final Runnable callback;
synchronized (mLock) {
- Preconditions.checkState(mStageDirInUse);
+ if (!mStageDirInUse) {
+ return false;
+ }
mStageDirInUse = false;
callback = mPendingAbandonCallback;
mPendingAbandonCallback = null;
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index cb87ff5467a8..0574f737a54b 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -92,7 +92,7 @@ final class PackageMetrics {
final long apksSize = getApksSize(ps.getPath());
FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
- 0 /* session_id */,
+ mInstallRequest.getSessionId() /* session_id */,
success ? null : packageName /* not report package_name on success */,
mInstallRequest.getUid() /* uid */,
newUsers /* user_ids */,
@@ -110,7 +110,7 @@ final class PackageMetrics {
installerUid /* installer_package_uid */,
-1 /* original_installer_package_uid */,
mInstallRequest.getDataLoaderType() /* data_loader_type */,
- 0 /* user_action_required_type */,
+ mInstallRequest.getRequireUserAction() /* user_action_required_type */,
mInstallRequest.isInstantInstall() /* is_instant */,
mInstallRequest.isInstallReplace() /* is_replace */,
mInstallRequest.isInstallSystem() /* is_system */,
diff --git a/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java b/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
index 8218fa5c4643..80d959977b0f 100644
--- a/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
+++ b/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
@@ -19,20 +19,15 @@ package com.android.server.timezonedetector;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.ShellCommand;
-import android.os.SystemClock;
-import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
-import java.util.StringTokenizer;
/**
- * A time zone suggestion from the location_time_zone_manager service to the time_zone_detector
- * service.
+ * A time zone suggestion from the location_time_zone_manager service (AKA the location-based time
+ * zone detection algorithm).
*
* <p>Geolocation-based suggestions have the following properties:
*
@@ -63,24 +58,16 @@ import java.util.StringTokenizer;
* location_time_zone_manager may become uncertain if components further downstream cannot
* determine the device's location with sufficient accuracy, or if the location is known but no
* time zone can be determined because no time zone mapping information is available.</li>
- * <li>{@code debugInfo} contains debugging metadata associated with the suggestion. This is
- * used to record why the suggestion exists and how it was obtained. This information exists
- * only to aid in debugging and therefore is used by {@link #toString()}, but it is not for use
- * in detection logic and is not considered in {@link #hashCode()} or {@link #equals(Object)}.
* </li>
* </ul>
- *
- * @hide
*/
public final class GeolocationTimeZoneSuggestion {
@ElapsedRealtimeLong private final long mEffectiveFromElapsedMillis;
@Nullable private final List<String> mZoneIds;
- @Nullable private ArrayList<String> mDebugInfo;
private GeolocationTimeZoneSuggestion(
- @ElapsedRealtimeLong long effectiveFromElapsedMillis,
- @Nullable List<String> zoneIds) {
+ @ElapsedRealtimeLong long effectiveFromElapsedMillis, @Nullable List<String> zoneIds) {
mEffectiveFromElapsedMillis = effectiveFromElapsedMillis;
if (zoneIds == null) {
// Unopinionated
@@ -104,8 +91,7 @@ public final class GeolocationTimeZoneSuggestion {
*/
@NonNull
public static GeolocationTimeZoneSuggestion createCertainSuggestion(
- @ElapsedRealtimeLong long effectiveFromElapsedMillis,
- @NonNull List<String> zoneIds) {
+ @ElapsedRealtimeLong long effectiveFromElapsedMillis, @NonNull List<String> zoneIds) {
return new GeolocationTimeZoneSuggestion(effectiveFromElapsedMillis, zoneIds);
}
@@ -126,25 +112,6 @@ public final class GeolocationTimeZoneSuggestion {
return mZoneIds;
}
- /** Returns debug information. See {@link GeolocationTimeZoneSuggestion} for details. */
- @NonNull
- public List<String> getDebugInfo() {
- return mDebugInfo == null
- ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
- }
-
- /**
- * Associates information with the instance that can be useful for debugging / logging. The
- * information is present in {@link #toString()} but is not considered for
- * {@link #equals(Object)} and {@link #hashCode()}.
- */
- public void addDebugInfo(String... debugInfos) {
- if (mDebugInfo == null) {
- mDebugInfo = new ArrayList<>();
- }
- mDebugInfo.addAll(Arrays.asList(debugInfos));
- }
-
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -169,59 +136,6 @@ public final class GeolocationTimeZoneSuggestion {
return "GeolocationTimeZoneSuggestion{"
+ "mEffectiveFromElapsedMillis=" + mEffectiveFromElapsedMillis
+ ", mZoneIds=" + mZoneIds
- + ", mDebugInfo=" + mDebugInfo
+ '}';
}
-
- /** @hide */
- public static GeolocationTimeZoneSuggestion parseCommandLineArg(@NonNull ShellCommand cmd) {
- String zoneIdsString = null;
- String opt;
- while ((opt = cmd.getNextArg()) != null) {
- switch (opt) {
- case "--zone_ids": {
- zoneIdsString = cmd.getNextArgRequired();
- break;
- }
- default: {
- throw new IllegalArgumentException("Unknown option: " + opt);
- }
- }
- }
-
- if (zoneIdsString == null) {
- throw new IllegalArgumentException("Missing --zone_ids");
- }
-
- long elapsedRealtimeMillis = SystemClock.elapsedRealtime();
- List<String> zoneIds = parseZoneIdsArg(zoneIdsString);
- GeolocationTimeZoneSuggestion suggestion =
- new GeolocationTimeZoneSuggestion(elapsedRealtimeMillis, zoneIds);
- suggestion.addDebugInfo("Command line injection");
- return suggestion;
- }
-
- private static List<String> parseZoneIdsArg(String zoneIdsString) {
- if ("UNCERTAIN".equals(zoneIdsString)) {
- return null;
- } else if ("EMPTY".equals(zoneIdsString)) {
- return Collections.emptyList();
- } else {
- ArrayList<String> zoneIds = new ArrayList<>();
- StringTokenizer tokenizer = new StringTokenizer(zoneIdsString, ",");
- while (tokenizer.hasMoreTokens()) {
- zoneIds.add(tokenizer.nextToken());
- }
- return zoneIds;
- }
- }
-
- /** @hide */
- public static void printCommandLineOpts(@NonNull PrintWriter pw) {
- pw.println("Geolocation suggestion options:");
- pw.println(" --zone_ids {UNCERTAIN|EMPTY|<Olson ID>+}");
- pw.println();
- pw.println("See " + GeolocationTimeZoneSuggestion.class.getName()
- + " for more information");
- }
}
diff --git a/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java b/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java
new file mode 100644
index 000000000000..1ffd9a11b300
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java
@@ -0,0 +1,194 @@
+/*
+ * 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.timezonedetector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringTokenizer;
+
+/**
+ * An event from the location_time_zone_manager service (AKA the location-based time zone detection
+ * algorithm). An event can represent a new time zone recommendation, an algorithm status change, or
+ * both.
+ *
+ * <p>Events have the following properties:
+ *
+ * <ul>
+ * <li>{@code algorithmStatus}: The current status of the location-based time zone detection
+ * algorithm.</li>
+ * <li>{@code suggestion}: The latest time zone suggestion, if there is one.</li>
+ * <li>{@code debugInfo} contains debugging metadata associated with the suggestion. This is
+ * used to record why the event exists and how information contained within it was obtained.
+ * This information exists only to aid in debugging and therefore is used by
+ * {@link #toString()}, but it is not for use in detection logic and is not considered in
+ * {@link #hashCode()} or {@link #equals(Object)}.
+ * </li>
+ * </ul>
+ */
+public final class LocationAlgorithmEvent {
+
+ @NonNull private final LocationTimeZoneAlgorithmStatus mAlgorithmStatus;
+ @Nullable private final GeolocationTimeZoneSuggestion mSuggestion;
+ @Nullable private ArrayList<String> mDebugInfo;
+
+ /** Creates a new instance. */
+ public LocationAlgorithmEvent(
+ @NonNull LocationTimeZoneAlgorithmStatus algorithmStatus,
+ @Nullable GeolocationTimeZoneSuggestion suggestion) {
+ mAlgorithmStatus = Objects.requireNonNull(algorithmStatus);
+ mSuggestion = suggestion;
+ }
+
+ /**
+ * Returns the status of the location time zone detector algorithm.
+ */
+ @NonNull
+ public LocationTimeZoneAlgorithmStatus getAlgorithmStatus() {
+ return mAlgorithmStatus;
+ }
+
+ /**
+ * Returns the latest location algorithm suggestion. See {@link LocationAlgorithmEvent} for
+ * details.
+ */
+ @Nullable
+ public GeolocationTimeZoneSuggestion getSuggestion() {
+ return mSuggestion;
+ }
+
+ /** Returns debug information. See {@link LocationAlgorithmEvent} for details. */
+ @NonNull
+ public List<String> getDebugInfo() {
+ return mDebugInfo == null
+ ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
+ }
+
+ /**
+ * Associates information with the instance that can be useful for debugging / logging. The
+ * information is present in {@link #toString()} but is not considered for
+ * {@link #equals(Object)} and {@link #hashCode()}.
+ */
+ public void addDebugInfo(String... debugInfos) {
+ if (mDebugInfo == null) {
+ mDebugInfo = new ArrayList<>();
+ }
+ mDebugInfo.addAll(Arrays.asList(debugInfos));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ LocationAlgorithmEvent that = (LocationAlgorithmEvent) o;
+ return mAlgorithmStatus.equals(that.mAlgorithmStatus)
+ && Objects.equals(mSuggestion, that.mSuggestion);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAlgorithmStatus, mSuggestion);
+ }
+
+ @Override
+ public String toString() {
+ return "LocationAlgorithmEvent{"
+ + "mAlgorithmStatus=" + mAlgorithmStatus
+ + ", mSuggestion=" + mSuggestion
+ + ", mDebugInfo=" + mDebugInfo
+ + '}';
+ }
+
+ static LocationAlgorithmEvent parseCommandLineArg(@NonNull ShellCommand cmd) {
+ String suggestionString = null;
+ LocationTimeZoneAlgorithmStatus algorithmStatus = null;
+ String opt;
+ while ((opt = cmd.getNextArg()) != null) {
+ switch (opt) {
+ case "--status": {
+ algorithmStatus = LocationTimeZoneAlgorithmStatus.parseCommandlineArg(
+ cmd.getNextArgRequired());
+ break;
+ }
+ case "--suggestion": {
+ suggestionString = cmd.getNextArgRequired();
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + opt);
+ }
+ }
+ }
+
+ if (algorithmStatus == null) {
+ throw new IllegalArgumentException("Missing --status");
+ }
+
+ GeolocationTimeZoneSuggestion suggestion = null;
+ if (suggestionString != null) {
+ List<String> zoneIds = parseZoneIds(suggestionString);
+ long elapsedRealtimeMillis = SystemClock.elapsedRealtime();
+ if (zoneIds == null) {
+ suggestion = GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+ elapsedRealtimeMillis);
+ } else {
+ suggestion = GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ elapsedRealtimeMillis, zoneIds);
+ }
+ }
+
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
+ event.addDebugInfo("Command line injection");
+ return event;
+ }
+
+ private static List<String> parseZoneIds(String zoneIdsString) {
+ if ("UNCERTAIN".equals(zoneIdsString)) {
+ return null;
+ } else if ("EMPTY".equals(zoneIdsString)) {
+ return Collections.emptyList();
+ } else {
+ ArrayList<String> zoneIds = new ArrayList<>();
+ StringTokenizer tokenizer = new StringTokenizer(zoneIdsString, ",");
+ while (tokenizer.hasMoreTokens()) {
+ zoneIds.add(tokenizer.nextToken());
+ }
+ return zoneIds;
+ }
+ }
+
+ static void printCommandLineOpts(@NonNull PrintWriter pw) {
+ pw.println("Location algorithm event options:");
+ pw.println(" --status {LocationTimeZoneAlgorithmStatus toString() format}");
+ pw.println(" [--suggestion {UNCERTAIN|EMPTY|<Olson ID>+}]");
+ pw.println();
+ pw.println("See " + LocationAlgorithmEvent.class.getName() + " for more information");
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
index 6c36989320e8..aad53596fc19 100644
--- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
+++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
@@ -89,7 +89,7 @@ public final class MetricsTimeZoneDetectorState {
@NonNull String deviceTimeZoneId,
@Nullable ManualTimeZoneSuggestion latestManualSuggestion,
@Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion,
- @Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion) {
+ @Nullable LocationAlgorithmEvent latestLocationAlgorithmEvent) {
boolean includeZoneIds = configurationInternal.isEnhancedMetricsCollectionEnabled();
String metricDeviceTimeZoneId = includeZoneIds ? deviceTimeZoneId : null;
@@ -101,9 +101,13 @@ public final class MetricsTimeZoneDetectorState {
MetricsTimeZoneSuggestion latestCanonicalTelephonySuggestion =
createMetricsTimeZoneSuggestion(
tzIdOrdinalGenerator, latestTelephonySuggestion, includeZoneIds);
- MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion =
- createMetricsTimeZoneSuggestion(
- tzIdOrdinalGenerator, latestGeolocationSuggestion, includeZoneIds);
+
+ MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion = null;
+ if (latestLocationAlgorithmEvent != null) {
+ GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion();
+ latestCanonicalGeolocationSuggestion = createMetricsTimeZoneSuggestion(
+ tzIdOrdinalGenerator, suggestion, includeZoneIds);
+ }
return new MetricsTimeZoneDetectorState(
configurationInternal, deviceTimeZoneIdOrdinal, metricDeviceTimeZoneId,
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
index 80cf1d6b9031..74a518bf8382 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
@@ -59,11 +59,11 @@ public interface TimeZoneDetectorInternal {
boolean setManualTimeZoneForDpm(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion);
/**
- * Suggests the current time zone, determined using geolocation, to the detector. The
- * detector may ignore the signal based on system settings, whether better information is
- * available, and so on. This method may be implemented asynchronously.
+ * Handles the supplied {@link LocationAlgorithmEvent}. The detector may ignore the event based
+ * on system settings, whether better information is available, and so on. This method may be
+ * implemented asynchronously.
*/
- void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion);
+ void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent locationAlgorithmEvent);
/** Generates a state snapshot for metrics. */
@NonNull
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index dfb44df7b993..07d04737c3e2 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
@@ -76,13 +76,14 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter
}
@Override
- public void suggestGeolocationTimeZone(
- @NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
- Objects.requireNonNull(timeZoneSuggestion);
+ public void handleLocationAlgorithmEvent(
+ @NonNull LocationAlgorithmEvent locationAlgorithmEvent) {
+ Objects.requireNonNull(locationAlgorithmEvent);
// This call can take place on the mHandler thread because there is no return value.
mHandler.post(
- () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion));
+ () -> mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent(
+ locationAlgorithmEvent));
}
@Override
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index f415cf03fdec..f8c1c9269ff3 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -300,12 +300,13 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
}
/** Provided for command-line access. This is not exposed as a binder API. */
- void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
+ void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent locationAlgorithmEvent) {
enforceSuggestGeolocationTimeZonePermission();
- Objects.requireNonNull(timeZoneSuggestion);
+ Objects.requireNonNull(locationAlgorithmEvent);
mHandler.post(
- () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion));
+ () -> mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent(
+ locationAlgorithmEvent));
}
@Override
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index 1b9f8e6cd66f..69274dba7825 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -19,6 +19,7 @@ import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_CONFIR
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_DUMP_METRICS;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_GET_TIME_ZONE_STATE;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED;
@@ -27,7 +28,6 @@ import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SERVIC
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_TIME_ZONE_STATE;
-import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
@@ -79,8 +79,8 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
return runIsGeoDetectionEnabled();
case SHELL_COMMAND_SET_GEO_DETECTION_ENABLED:
return runSetGeoDetectionEnabled();
- case SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE:
- return runSuggestGeolocationTimeZone();
+ case SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT:
+ return runHandleLocationEvent();
case SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE:
return runSuggestManualTimeZone();
case SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE:
@@ -153,34 +153,34 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
return mInterface.updateConfiguration(userId, configuration) ? 0 : 1;
}
- private int runSuggestGeolocationTimeZone() {
- return runSuggestTimeZone(
- () -> GeolocationTimeZoneSuggestion.parseCommandLineArg(this),
- mInterface::suggestGeolocationTimeZone);
+ private int runHandleLocationEvent() {
+ return runSingleArgMethod(
+ () -> LocationAlgorithmEvent.parseCommandLineArg(this),
+ mInterface::handleLocationAlgorithmEvent);
}
private int runSuggestManualTimeZone() {
- return runSuggestTimeZone(
+ return runSingleArgMethod(
() -> ManualTimeZoneSuggestion.parseCommandLineArg(this),
mInterface::suggestManualTimeZone);
}
private int runSuggestTelephonyTimeZone() {
- return runSuggestTimeZone(
+ return runSingleArgMethod(
() -> TelephonyTimeZoneSuggestion.parseCommandLineArg(this),
mInterface::suggestTelephonyTimeZone);
}
- private <T> int runSuggestTimeZone(Supplier<T> suggestionParser, Consumer<T> invoker) {
+ private <T> int runSingleArgMethod(Supplier<T> argParser, Consumer<T> invoker) {
final PrintWriter pw = getOutPrintWriter();
try {
- T suggestion = suggestionParser.get();
- if (suggestion == null) {
- pw.println("Error: suggestion not specified");
+ T arg = argParser.get();
+ if (arg == null) {
+ pw.println("Error: arg not specified");
return 1;
}
- invoker.accept(suggestion);
- pw.println("Suggestion " + suggestion + " injected.");
+ invoker.accept(arg);
+ pw.println("Arg " + arg + " injected.");
return 0;
} catch (RuntimeException e) {
pw.println(e);
@@ -263,18 +263,18 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
pw.printf(" Sets the geolocation time zone detection enabled setting.\n");
pw.printf(" %s\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK);
pw.printf(" Signals that telephony time zone detection fall back can be used if"
- + " geolocation detection is supported and enabled. This is a temporary state until"
- + " geolocation detection becomes \"certain\". To have an effect this requires that"
- + " the telephony fallback feature is supported on the device, see below for"
- + " for device_config flags.\n");
- pw.println();
- pw.printf(" %s <geolocation suggestion opts>\n",
- SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE);
- pw.printf(" Suggests a time zone as if via the \"location\" origin.\n");
+ + " geolocation detection is supported and enabled.\n)");
+ pw.printf(" This is a temporary state until geolocation detection becomes \"certain\"."
+ + "\n");
+ pw.printf(" To have an effect this requires that the telephony fallback feature is"
+ + " supported on the device, see below for device_config flags.\n");
+ pw.printf(" %s <location event opts>\n", SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT);
+ pw.printf(" Simulates an event from the location time zone detection algorithm.\n");
pw.printf(" %s <manual suggestion opts>\n", SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE);
- pw.printf(" Suggests a time zone as if via the \"manual\" origin.\n");
+ pw.printf(" Suggests a time zone as if supplied by a user manually.\n");
pw.printf(" %s <telephony suggestion opts>\n", SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
- pw.printf(" Suggests a time zone as if via the \"telephony\" origin.\n");
+ pw.printf(" Simulates a time zone suggestion from the telephony time zone detection"
+ + " algorithm.\n");
pw.printf(" %s\n", SHELL_COMMAND_GET_TIME_ZONE_STATE);
pw.printf(" Returns the current time zone setting state.\n");
pw.printf(" %s <time zone state options>\n", SHELL_COMMAND_SET_TIME_ZONE_STATE);
@@ -284,7 +284,7 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
pw.printf(" %s\n", SHELL_COMMAND_DUMP_METRICS);
pw.printf(" Dumps the service metrics to stdout for inspection.\n");
pw.println();
- GeolocationTimeZoneSuggestion.printCommandLineOpts(pw);
+ LocationAlgorithmEvent.printCommandLineOpts(pw);
pw.println();
ManualTimeZoneSuggestion.printCommandLineOpts(pw);
pw.println();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 328cf72b262e..5768a6bfa0dc 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -157,10 +157,9 @@ public interface TimeZoneDetectorStrategy extends Dumpable {
boolean confirmTimeZone(@NonNull String timeZoneId);
/**
- * Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if
- * {@link GeolocationTimeZoneSuggestion#getZoneIds()} is {@code null}.
+ * Handles an event from the location-based time zone detection algorithm.
*/
- void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion suggestion);
+ void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent event);
/**
* Suggests a time zone for the device using manually-entered (i.e. user sourced) information.
@@ -183,7 +182,7 @@ public interface TimeZoneDetectorStrategy extends Dumpable {
/**
* Tells the strategy that it can fall back to telephony detection while geolocation detection
- * remains uncertain. {@link #suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion)} can
+ * remains uncertain. {@link #handleLocationAlgorithmEvent(LocationAlgorithmEvent)} can
* disable it again. See {@link TimeZoneDetectorStrategy} for details.
*/
void enableTelephonyTimeZoneFallback();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index ecf25e9c157c..eecf0f74bff6 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -29,9 +29,13 @@ import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.time.DetectorStatusTypes;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
import android.app.time.TimeZoneCapabilities;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -183,11 +187,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
/**
- * The latest geolocation suggestion received. If the user disabled geolocation time zone
- * detection then the latest suggestion is cleared.
+ * The latest location algorithm event received.
*/
@GuardedBy("this")
- private final ReferenceWithHistory<GeolocationTimeZoneSuggestion> mLatestGeoLocationSuggestion =
+ private final ReferenceWithHistory<LocationAlgorithmEvent> mLatestLocationAlgorithmEvent =
new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
/**
@@ -208,6 +211,14 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
@NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
/**
+ * A snapshot of the current detector status. A local copy is cached because it is relatively
+ * heavyweight to obtain and is used more often than it is expected to change.
+ */
+ @GuardedBy("this")
+ @NonNull
+ private TimeZoneDetectorStatus mDetectorStatus;
+
+ /**
* A snapshot of the current user's {@link ConfigurationInternal}. A local copy is cached
* because it is relatively heavyweight to obtain and is used more often than it is expected to
* change. Because many operations are asynchronous, this value may be out of date but should
@@ -258,8 +269,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
// Listen for config and user changes and get an initial snapshot of configuration.
StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged;
mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
- mCurrentConfigurationInternal =
- mServiceConfigAccessor.getCurrentUserConfigurationInternal();
+
+ // Initialize mCurrentConfigurationInternal and mDetectorStatus with their starting
+ // values.
+ updateCurrentConfigurationInternalIfRequired("TimeZoneDetectorStrategyImpl:");
}
}
@@ -278,6 +291,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId);
}
return new TimeZoneCapabilitiesAndConfig(
+ mDetectorStatus,
configurationInternal.asCapabilities(bypassUserPolicyChecks),
configurationInternal.asConfiguration());
}
@@ -295,28 +309,52 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
// but that could mean an immediate call to getCapabilitiesAndConfig() for the current user
// wouldn't see the update. So, handle the cache update and notifications here. When the
// async update listener triggers it will find everything already up to date and do nothing.
- if (updateSuccessful && mCurrentConfigurationInternal.getUserId() == userId) {
- ConfigurationInternal configurationInternal =
- mServiceConfigAccessor.getConfigurationInternal(userId);
-
- // If the configuration actually changed, update the cached copy synchronously and do
- // other necessary house-keeping / (async) listener notifications.
- if (!configurationInternal.equals(mCurrentConfigurationInternal)) {
- mCurrentConfigurationInternal = configurationInternal;
-
- String logMsg = "updateConfiguration:"
- + " userId=" + userId
- + ", configuration=" + configuration
- + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks
- + ", mCurrentConfigurationInternal=" + mCurrentConfigurationInternal;
- logTimeZoneDebugInfo(logMsg);
-
- handleConfigurationInternalChanged(logMsg);
- }
+ if (updateSuccessful) {
+ String logMsg = "updateConfiguration:"
+ + " userId=" + userId
+ + ", configuration=" + configuration
+ + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks;
+ updateCurrentConfigurationInternalIfRequired(logMsg);
}
return updateSuccessful;
}
+ @GuardedBy("this")
+ private void updateCurrentConfigurationInternalIfRequired(@NonNull String logMsg) {
+ ConfigurationInternal newCurrentConfigurationInternal =
+ mServiceConfigAccessor.getCurrentUserConfigurationInternal();
+ // mCurrentConfigurationInternal is null the first time this method is called.
+ ConfigurationInternal oldCurrentConfigurationInternal = mCurrentConfigurationInternal;
+
+ // If the configuration actually changed, update the cached copy synchronously and do
+ // other necessary house-keeping / (async) listener notifications.
+ if (!newCurrentConfigurationInternal.equals(oldCurrentConfigurationInternal)) {
+ mCurrentConfigurationInternal = newCurrentConfigurationInternal;
+
+ logMsg += " [oldConfiguration=" + oldCurrentConfigurationInternal
+ + ", newConfiguration=" + newCurrentConfigurationInternal
+ + "]";
+ logTimeZoneDebugInfo(logMsg);
+
+ // ConfigurationInternal changes can affect the detector's status.
+ updateDetectorStatus();
+
+ // The configuration and maybe the status changed so notify listeners.
+ notifyStateChangeListenersAsynchronously();
+
+ // The configuration change may have changed available suggestions or the way
+ // suggestions are used, so re-run detection.
+ doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg);
+ }
+ }
+
+ @GuardedBy("this")
+ private void notifyStateChangeListenersAsynchronously() {
+ for (StateChangeListener listener : mStateChangeListeners) {
+ mStateChangeHandler.post(listener::onChange);
+ }
+ }
+
@Override
public synchronized void addChangeListener(StateChangeListener listener) {
mStateChangeListeners.add(listener);
@@ -356,33 +394,39 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
}
@Override
- public synchronized void suggestGeolocationTimeZone(
- @NonNull GeolocationTimeZoneSuggestion suggestion) {
-
+ public synchronized void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent event) {
ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal;
if (DBG) {
- Slog.d(LOG_TAG, "Geolocation suggestion received."
+ Slog.d(LOG_TAG, "Location algorithm event received."
+ " currentUserConfig=" + currentUserConfig
- + " newSuggestion=" + suggestion);
+ + " event=" + event);
}
- Objects.requireNonNull(suggestion);
+ Objects.requireNonNull(event);
- // Geolocation suggestions may be stored but not used during time zone detection if the
+ // Location algorithm events may be stored but not used during time zone detection if the
// configuration doesn't have geo time zone detection enabled. The caller is expected to
- // withdraw a previous suggestion (i.e. submit an "uncertain" suggestion, when geo time zone
- // detection is disabled.
-
- // The suggestion's "effective from" time is ignored: we currently assume suggestions
- // are made in a sensible order and the most recent is always the best one to use.
- mLatestGeoLocationSuggestion.set(suggestion);
+ // withdraw a previous suggestion, i.e. submit an event containing an "uncertain"
+ // suggestion, when geo time zone detection is disabled.
+
+ // We currently assume events are made in a sensible order and the most recent is always the
+ // best one to use.
+ mLatestLocationAlgorithmEvent.set(event);
+
+ // The latest location algorithm event can affect the cached detector status, so update it
+ // and notify state change listeners as needed.
+ boolean statusChanged = updateDetectorStatus();
+ if (statusChanged) {
+ notifyStateChangeListenersAsynchronously();
+ }
// Update the mTelephonyTimeZoneFallbackEnabled state if needed: a certain suggestion
// will usually disable telephony fallback mode if it is currently enabled.
+ // TODO(b/236624675)Some provider status codes can be used to enable telephony fallback.
disableTelephonyFallbackIfNeeded();
- // Now perform auto time zone detection. The new suggestion may be used to modify the
- // time zone setting.
- String reason = "New geolocation time zone suggested. suggestion=" + suggestion;
+ // Now perform auto time zone detection. The new event may be used to modify the time zone
+ // setting.
+ String reason = "New location algorithm event received. event=" + event;
doAutoTimeZoneDetection(currentUserConfig, reason);
}
@@ -465,9 +509,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
+ mTelephonyTimeZoneFallbackEnabled;
logTimeZoneDebugInfo(logMsg);
- // mTelephonyTimeZoneFallbackEnabled and mLatestGeoLocationSuggestion interact.
- // If there is currently a certain geolocation suggestion, then the telephony fallback
- // value needs to be considered after changing it.
+ // mTelephonyTimeZoneFallbackEnabled and mLatestLocationAlgorithmEvent interact.
+ // If the latest event contains a "certain" geolocation suggestion, then the telephony
+ // fallback value needs to be considered after changing it.
// With the way that the mTelephonyTimeZoneFallbackEnabled time is currently chosen
// above, and the fact that geolocation suggestions should never have a time in the
// future, the following call will be a no-op, and telephony fallback will remain
@@ -507,7 +551,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
mEnvironment.getDeviceTimeZone(),
getLatestManualSuggestion(),
telephonySuggestion,
- getLatestGeolocationSuggestion());
+ getLatestLocationAlgorithmEvent());
}
@Override
@@ -606,13 +650,15 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
*/
@GuardedBy("this")
private boolean doGeolocationTimeZoneDetection(@NonNull String detectionReason) {
- GeolocationTimeZoneSuggestion latestGeolocationSuggestion =
- mLatestGeoLocationSuggestion.get();
- if (latestGeolocationSuggestion == null) {
+ // Terminate early if there's nothing to do.
+ LocationAlgorithmEvent latestLocationAlgorithmEvent = mLatestLocationAlgorithmEvent.get();
+ if (latestLocationAlgorithmEvent == null
+ || latestLocationAlgorithmEvent.getSuggestion() == null) {
return false;
}
- List<String> zoneIds = latestGeolocationSuggestion.getZoneIds();
+ GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion();
+ List<String> zoneIds = suggestion.getZoneIds();
if (zoneIds == null) {
// This means the originator of the suggestion is uncertain about the time zone. The
// existing time zone setting must be left as it is but detection can go on looking for
@@ -645,13 +691,18 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
}
/**
- * Sets the mTelephonyTimeZoneFallbackEnabled state to {@code false} if the latest geo
- * suggestion is a "certain" suggestion that comes after the time when telephony fallback was
- * enabled.
+ * Sets the mTelephonyTimeZoneFallbackEnabled state to {@code false} if the latest location
+ * algorithm event contains a "certain" suggestion that comes after the time when telephony
+ * fallback was enabled.
*/
@GuardedBy("this")
private void disableTelephonyFallbackIfNeeded() {
- GeolocationTimeZoneSuggestion suggestion = mLatestGeoLocationSuggestion.get();
+ LocationAlgorithmEvent latestLocationAlgorithmEvent = mLatestLocationAlgorithmEvent.get();
+ if (latestLocationAlgorithmEvent == null) {
+ return;
+ }
+
+ GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion();
boolean isLatestSuggestionCertain = suggestion != null && suggestion.getZoneIds() != null;
if (isLatestSuggestionCertain && mTelephonyTimeZoneFallbackEnabled.getValue()) {
// This transition ONLY changes mTelephonyTimeZoneFallbackEnabled from
@@ -809,33 +860,27 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* Handles a configuration change notification.
*/
private synchronized void handleConfigurationInternalMaybeChanged() {
- ConfigurationInternal currentUserConfig =
- mServiceConfigAccessor.getCurrentUserConfigurationInternal();
-
- // The configuration may not actually have changed so check before doing anything.
- if (!currentUserConfig.equals(mCurrentConfigurationInternal)) {
- String logMsg = "handleConfigurationInternalMaybeChanged:"
- + " oldConfiguration=" + mCurrentConfigurationInternal
- + ", newConfiguration=" + currentUserConfig;
- logTimeZoneDebugInfo(logMsg);
-
- mCurrentConfigurationInternal = currentUserConfig;
-
- handleConfigurationInternalChanged(logMsg);
- }
+ String logMsg = "handleConfigurationInternalMaybeChanged:";
+ updateCurrentConfigurationInternalIfRequired(logMsg);
}
- /** House-keeping that needs to be done when the mCurrentConfigurationInternal has changed. */
+ /**
+ * Called whenever the information that contributes to {@link #mDetectorStatus} could have
+ * changed. Updates the cached status snapshot if required.
+ *
+ * @return true if the status had changed and has been updated
+ */
@GuardedBy("this")
- private void handleConfigurationInternalChanged(@NonNull String logMsg) {
- // Notify change listeners asynchronously.
- for (StateChangeListener listener : mStateChangeListeners) {
- mStateChangeHandler.post(listener::onChange);
+ private boolean updateDetectorStatus() {
+ TimeZoneDetectorStatus newDetectorStatus = createTimeZoneDetectorStatus(
+ mCurrentConfigurationInternal, mLatestLocationAlgorithmEvent.get());
+ // mDetectorStatus is null the first time this method is called.
+ TimeZoneDetectorStatus oldDetectorStatus = mDetectorStatus;
+ boolean statusChanged = !newDetectorStatus.equals(oldDetectorStatus);
+ if (statusChanged) {
+ mDetectorStatus = newDetectorStatus;
}
-
- // The configuration change may have changed available suggestions or the way
- // suggestions are used, so re-run detection.
- doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg);
+ return statusChanged;
}
/**
@@ -847,6 +892,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
ipw.increaseIndent(); // level 1
ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal);
+ ipw.println("mDetectorStatus=" + mDetectorStatus);
final boolean bypassUserPolicyChecks = false;
ipw.println("[Capabilities="
+ mCurrentConfigurationInternal.asCapabilities(bypassUserPolicyChecks) + "]");
@@ -870,9 +916,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
mLatestManualSuggestion.dump(ipw);
ipw.decreaseIndent(); // level 2
- ipw.println("Geolocation suggestion history:");
+ ipw.println("Location algorithm event history:");
ipw.increaseIndent(); // level 2
- mLatestGeoLocationSuggestion.dump(ipw);
+ mLatestLocationAlgorithmEvent.dump(ipw);
ipw.decreaseIndent(); // level 2
ipw.println("Telephony suggestion history:");
@@ -886,6 +932,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* A method used to inspect strategy state during tests. Not intended for general use.
*/
@VisibleForTesting
+ @Nullable
public synchronized ManualTimeZoneSuggestion getLatestManualSuggestion() {
return mLatestManualSuggestion.get();
}
@@ -894,6 +941,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* A method used to inspect strategy state during tests. Not intended for general use.
*/
@VisibleForTesting
+ @Nullable
public synchronized QualifiedTelephonyTimeZoneSuggestion getLatestTelephonySuggestion(
int slotIndex) {
return mTelephonySuggestionsBySlotIndex.get(slotIndex);
@@ -903,8 +951,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* A method used to inspect strategy state during tests. Not intended for general use.
*/
@VisibleForTesting
- public synchronized GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() {
- return mLatestGeoLocationSuggestion.get();
+ @Nullable
+ public synchronized LocationAlgorithmEvent getLatestLocationAlgorithmEvent() {
+ return mLatestLocationAlgorithmEvent.get();
}
@VisibleForTesting
@@ -917,6 +966,11 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
return mCurrentConfigurationInternal;
}
+ @VisibleForTesting
+ public synchronized TimeZoneDetectorStatus getCachedDetectorStatusForTests() {
+ return mDetectorStatus;
+ }
+
/**
* A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
*/
@@ -970,4 +1024,42 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
private static String formatDebugString(TimestampedValue<?> value) {
return value.getValue() + " @ " + Duration.ofMillis(value.getReferenceTimeMillis());
}
+
+ @NonNull
+ private static TimeZoneDetectorStatus createTimeZoneDetectorStatus(
+ @NonNull ConfigurationInternal currentConfigurationInternal,
+ @Nullable LocationAlgorithmEvent latestLocationAlgorithmEvent) {
+
+ int detectorStatus;
+ if (!currentConfigurationInternal.isAutoDetectionSupported()) {
+ detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_NOT_SUPPORTED;
+ } else if (currentConfigurationInternal.getAutoDetectionEnabledBehavior()) {
+ detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+ } else {
+ detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_NOT_RUNNING;
+ }
+
+ TelephonyTimeZoneAlgorithmStatus telephonyAlgorithmStatus =
+ createTelephonyAlgorithmStatus(currentConfigurationInternal);
+
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ latestLocationAlgorithmEvent == null ? LocationTimeZoneAlgorithmStatus.UNKNOWN
+ : latestLocationAlgorithmEvent.getAlgorithmStatus();
+
+ return new TimeZoneDetectorStatus(
+ detectorStatus, telephonyAlgorithmStatus, locationAlgorithmStatus);
+ }
+
+ @NonNull
+ private static TelephonyTimeZoneAlgorithmStatus createTelephonyAlgorithmStatus(
+ @NonNull ConfigurationInternal currentConfigurationInternal) {
+ int algorithmStatus;
+ if (!currentConfigurationInternal.isTelephonyDetectionSupported()) {
+ algorithmStatus = DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+ } else {
+ // The telephony detector is passive, so we treat it as "running".
+ algorithmStatus = DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+ }
+ return new TelephonyTimeZoneAlgorithmStatus(algorithmStatus);
+ }
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
index a1de2941808e..71aa10d8614a 100644
--- a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
@@ -53,7 +53,7 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
}
@Override
- void onInitialize() {
+ boolean onInitialize() {
mProxy.initialize(new LocationTimeZoneProviderProxy.Listener() {
@Override
public void onReportTimeZoneProviderEvent(
@@ -71,6 +71,7 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
handleTemporaryFailure("onProviderUnbound()");
}
});
+ return true;
}
@Override
diff --git a/services/core/java/com/android/server/timezonedetector/location/DisabledLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/DisabledLocationTimeZoneProvider.java
new file mode 100644
index 000000000000..5d6184ec66d6
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/location/DisabledLocationTimeZoneProvider.java
@@ -0,0 +1,86 @@
+/*
+ * 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.timezonedetector.location;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+
+import java.time.Duration;
+
+/**
+ * A {@link LocationTimeZoneProvider} that provides minimal responses needed to operate correctly
+ * when there is no "real" provider configured / enabled. This is used when the platform supports
+ * more providers than are needed for an Android deployment.
+ *
+ * <p>That is, the {@link LocationTimeZoneProviderController} supports a primary and a secondary
+ * {@link LocationTimeZoneProvider}, but if only a primary is configured, the secondary provider
+ * config will marked as "disabled" and the {@link LocationTimeZoneProvider} implementation will use
+ * {@link DisabledLocationTimeZoneProvider}. The {@link DisabledLocationTimeZoneProvider} fails
+ * initialization and immediately moves to a "permanent failure" state, which ensures the {@link
+ * LocationTimeZoneProviderController} correctly categorizes it and won't attempt to use it.
+ */
+class DisabledLocationTimeZoneProvider extends LocationTimeZoneProvider {
+
+ DisabledLocationTimeZoneProvider(
+ @NonNull ProviderMetricsLogger providerMetricsLogger,
+ @NonNull ThreadingDomain threadingDomain,
+ @NonNull String providerName,
+ boolean recordStateChanges) {
+ super(providerMetricsLogger, threadingDomain, providerName, x -> x, recordStateChanges);
+ }
+
+ @Override
+ boolean onInitialize() {
+ // Fail initialization, preventing further use.
+ return false;
+ }
+
+ @Override
+ void onDestroy() {
+ }
+
+ @Override
+ void onStartUpdates(@NonNull Duration initializationTimeout,
+ @NonNull Duration eventFilteringAgeThreshold) {
+ throw new UnsupportedOperationException("Provider is disabled");
+ }
+
+ @Override
+ void onStopUpdates() {
+ throw new UnsupportedOperationException("Provider is disabled");
+ }
+
+ @Override
+ public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
+ synchronized (mSharedLock) {
+ ipw.println("{DisabledLocationTimeZoneProvider}");
+ ipw.println("mProviderName=" + mProviderName);
+ ipw.println("mCurrentState=" + mCurrentState);
+ }
+ }
+
+ @Override
+ public String toString() {
+ synchronized (mSharedLock) {
+ return "DisabledLocationTimeZoneProvider{"
+ + "mProviderName=" + mProviderName
+ + ", mCurrentState=" + mCurrentState
+ + '}';
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
index 36ab111dfccb..8d9854436e29 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
@@ -447,11 +447,18 @@ public class LocationTimeZoneManagerService extends Binder {
@NonNull
LocationTimeZoneProvider createProvider() {
- LocationTimeZoneProviderProxy proxy = createProxy();
ProviderMetricsLogger providerMetricsLogger = new RealProviderMetricsLogger(mIndex);
- return new BinderLocationTimeZoneProvider(
- providerMetricsLogger, mThreadingDomain, mName, proxy,
- mServiceConfigAccessor.getRecordStateChangesForTests());
+
+ String mode = getMode();
+ if (Objects.equals(mode, PROVIDER_MODE_DISABLED)) {
+ return new DisabledLocationTimeZoneProvider(providerMetricsLogger, mThreadingDomain,
+ mName, mServiceConfigAccessor.getRecordStateChangesForTests());
+ } else {
+ LocationTimeZoneProviderProxy proxy = createBinderProxy();
+ return new BinderLocationTimeZoneProvider(
+ providerMetricsLogger, mThreadingDomain, mName, proxy,
+ mServiceConfigAccessor.getRecordStateChangesForTests());
+ }
}
@Override
@@ -460,17 +467,6 @@ public class LocationTimeZoneManagerService extends Binder {
ipw.printf("getPackageName()=%s\n", getPackageName());
}
- @NonNull
- private LocationTimeZoneProviderProxy createProxy() {
- String mode = getMode();
- if (Objects.equals(mode, PROVIDER_MODE_DISABLED)) {
- return new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
- } else {
- // mode == PROVIDER_MODE_OVERRIDE_ENABLED (or unknown).
- return createRealProxy();
- }
- }
-
/** Returns the mode of the provider (enabled/disabled). */
@NonNull
private String getMode() {
@@ -482,7 +478,7 @@ public class LocationTimeZoneManagerService extends Binder {
}
@NonNull
- private RealLocationTimeZoneProviderProxy createRealProxy() {
+ private RealLocationTimeZoneProviderProxy createBinderProxy() {
String providerServiceAction = mServiceAction;
boolean isTestProvider = isTestProvider();
String providerPackageName = getPackageName();
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
index 1f752f45fd51..e90a1fe34798 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
@@ -19,7 +19,7 @@ package com.android.server.timezonedetector.location;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState;
import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State;
@@ -32,14 +32,14 @@ import java.util.Objects;
final class LocationTimeZoneManagerServiceState {
private final @State String mControllerState;
- @Nullable private final GeolocationTimeZoneSuggestion mLastSuggestion;
+ @Nullable private final LocationAlgorithmEvent mLastEvent;
@NonNull private final List<@State String> mControllerStates;
@NonNull private final List<ProviderState> mPrimaryProviderStates;
@NonNull private final List<ProviderState> mSecondaryProviderStates;
LocationTimeZoneManagerServiceState(@NonNull Builder builder) {
mControllerState = builder.mControllerState;
- mLastSuggestion = builder.mLastSuggestion;
+ mLastEvent = builder.mLastEvent;
mControllerStates = Objects.requireNonNull(builder.mControllerStates);
mPrimaryProviderStates = Objects.requireNonNull(builder.mPrimaryProviderStates);
mSecondaryProviderStates = Objects.requireNonNull(builder.mSecondaryProviderStates);
@@ -50,8 +50,8 @@ final class LocationTimeZoneManagerServiceState {
}
@Nullable
- public GeolocationTimeZoneSuggestion getLastSuggestion() {
- return mLastSuggestion;
+ public LocationAlgorithmEvent getLastEvent() {
+ return mLastEvent;
}
@NonNull
@@ -73,7 +73,7 @@ final class LocationTimeZoneManagerServiceState {
public String toString() {
return "LocationTimeZoneManagerServiceState{"
+ "mControllerState=" + mControllerState
- + ", mLastSuggestion=" + mLastSuggestion
+ + ", mLastEvent=" + mLastEvent
+ ", mControllerStates=" + mControllerStates
+ ", mPrimaryProviderStates=" + mPrimaryProviderStates
+ ", mSecondaryProviderStates=" + mSecondaryProviderStates
@@ -83,7 +83,7 @@ final class LocationTimeZoneManagerServiceState {
static final class Builder {
private @State String mControllerState;
- private GeolocationTimeZoneSuggestion mLastSuggestion;
+ private LocationAlgorithmEvent mLastEvent;
private List<@State String> mControllerStates;
private List<ProviderState> mPrimaryProviderStates;
private List<ProviderState> mSecondaryProviderStates;
@@ -95,8 +95,8 @@ final class LocationTimeZoneManagerServiceState {
}
@NonNull
- Builder setLastSuggestion(@NonNull GeolocationTimeZoneSuggestion lastSuggestion) {
- mLastSuggestion = Objects.requireNonNull(lastSuggestion);
+ Builder setLastEvent(@NonNull LocationAlgorithmEvent lastEvent) {
+ mLastEvent = Objects.requireNonNull(lastEvent);
return this;
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
index 60bbea77b636..cefd0b578df8 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
@@ -15,6 +15,10 @@
*/
package com.android.server.timezonedetector.location;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
import static android.app.time.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO;
import static android.app.time.LocationTimeZoneManager.NULL_PACKAGE_NAME_TOKEN;
import static android.app.time.LocationTimeZoneManager.SERVICE_NAME;
@@ -51,9 +55,14 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneProvi
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
import android.app.time.GeolocationTimeZoneSuggestionProto;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.LocationTimeZoneAlgorithmStatusProto;
import android.app.time.LocationTimeZoneManagerProto;
import android.app.time.LocationTimeZoneManagerServiceStateProto;
+import android.app.time.LocationTimeZoneProviderEventProto;
+import android.app.time.TimeZoneDetectorProto;
import android.app.time.TimeZoneProviderStateProto;
import android.app.timezonedetector.TimeZoneDetector;
import android.os.ShellCommand;
@@ -62,6 +71,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State;
@@ -239,19 +249,39 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
outputStream = new DualDumpOutputStream(
new IndentingPrintWriter(getOutPrintWriter(), " "));
}
- if (state.getLastSuggestion() != null) {
- GeolocationTimeZoneSuggestion lastSuggestion = state.getLastSuggestion();
- long lastSuggestionToken = outputStream.start(
- "last_suggestion", LocationTimeZoneManagerServiceStateProto.LAST_SUGGESTION);
- for (String zoneId : lastSuggestion.getZoneIds()) {
- outputStream.write(
- "zone_ids" , GeolocationTimeZoneSuggestionProto.ZONE_IDS, zoneId);
+
+ if (state.getLastEvent() != null) {
+ LocationAlgorithmEvent lastEvent = state.getLastEvent();
+ long lastEventToken = outputStream.start(
+ "last_event", LocationTimeZoneManagerServiceStateProto.LAST_EVENT);
+
+ // lastEvent.algorithmStatus
+ LocationTimeZoneAlgorithmStatus algorithmStatus = lastEvent.getAlgorithmStatus();
+ long algorithmStatusToken = outputStream.start(
+ "algorithm_status", LocationTimeZoneProviderEventProto.ALGORITHM_STATUS);
+ outputStream.write("status", LocationTimeZoneAlgorithmStatusProto.STATUS,
+ convertDetectionAlgorithmStatusToEnumToProtoEnum(algorithmStatus.getStatus()));
+ outputStream.end(algorithmStatusToken);
+
+ // lastEvent.suggestion
+ if (lastEvent.getSuggestion() != null) {
+ long suggestionToken = outputStream.start(
+ "suggestion", LocationTimeZoneProviderEventProto.SUGGESTION);
+ GeolocationTimeZoneSuggestion lastSuggestion = lastEvent.getSuggestion();
+ for (String zoneId : lastSuggestion.getZoneIds()) {
+ outputStream.write(
+ "zone_ids", GeolocationTimeZoneSuggestionProto.ZONE_IDS, zoneId);
+ }
+ outputStream.end(suggestionToken);
}
- for (String debugInfo : lastSuggestion.getDebugInfo()) {
+
+ // lastEvent.debugInfo
+ for (String debugInfo : lastEvent.getDebugInfo()) {
outputStream.write(
- "debug_info", GeolocationTimeZoneSuggestionProto.DEBUG_INFO, debugInfo);
+ "debug_info", LocationTimeZoneProviderEventProto.DEBUG_INFO, debugInfo);
}
- outputStream.end(lastSuggestionToken);
+
+ outputStream.end(lastEventToken);
}
writeControllerStates(outputStream, state.getControllerStates());
@@ -330,6 +360,22 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
}
}
+ private static int convertDetectionAlgorithmStatusToEnumToProtoEnum(
+ @DetectionAlgorithmStatus int statusEnum) {
+ switch (statusEnum) {
+ case DETECTION_ALGORITHM_STATUS_UNKNOWN:
+ return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_UNKNOWN;
+ case DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED:
+ return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+ case DETECTION_ALGORITHM_STATUS_NOT_RUNNING:
+ return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+ case DETECTION_ALGORITHM_STATUS_RUNNING:
+ return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_RUNNING;
+ default:
+ throw new IllegalArgumentException("Unknown statusEnum=" + statusEnum);
+ }
+ }
+
private void reportError(@NonNull Throwable e) {
PrintWriter errPrintWriter = getErrPrintWriter();
errPrintWriter.println("Error: ");
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
index b1fc4f561033..ba7c328f1e80 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
@@ -16,6 +16,10 @@
package com.android.server.timezonedetector.location;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
@@ -33,6 +37,7 @@ import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus;
import android.os.Handler;
import android.os.SystemClock;
import android.service.timezone.TimeZoneProviderEvent;
@@ -295,6 +300,34 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|| stateEnum == PROVIDER_STATE_DESTROYED;
}
+ /**
+ * Maps the internal state enum value to one of the status values exposed to the layers
+ * above.
+ */
+ public @ProviderStatus int getProviderStatus() {
+ switch (stateEnum) {
+ case PROVIDER_STATE_STARTED_INITIALIZING:
+ return PROVIDER_STATUS_NOT_READY;
+ case PROVIDER_STATE_STARTED_CERTAIN:
+ return PROVIDER_STATUS_IS_CERTAIN;
+ case PROVIDER_STATE_STARTED_UNCERTAIN:
+ return PROVIDER_STATUS_IS_UNCERTAIN;
+ case PROVIDER_STATE_PERM_FAILED:
+ // Perm failed means the providers wasn't configured, configured properly,
+ // or has removed itself for other reasons, e.g. turned-down server.
+ return PROVIDER_STATUS_NOT_PRESENT;
+ case PROVIDER_STATE_STOPPED:
+ case PROVIDER_STATE_DESTROYED:
+ // This is a "safe" default that best describes a provider that isn't in one of
+ // the more obviously mapped states.
+ return PROVIDER_STATUS_NOT_READY;
+ case PROVIDER_STATE_UNKNOWN:
+ default:
+ throw new IllegalStateException(
+ "Unknown state enum:" + prettyPrintStateEnum(stateEnum));
+ }
+ }
+
/** Returns the status reported by the provider, if available. */
@Nullable
TimeZoneProviderStatus getReportedStatus() {
@@ -415,13 +448,21 @@ abstract class LocationTimeZoneProvider implements Dumpable {
currentState = currentState.newState(PROVIDER_STATE_STOPPED, null, null, "initialize");
setCurrentState(currentState, false);
+ boolean initializationSuccess;
+ String initializationFailureReason;
// Guard against uncaught exceptions due to initialization problems.
try {
- onInitialize();
+ initializationSuccess = onInitialize();
+ initializationFailureReason = "onInitialize() returned false";
} catch (RuntimeException e) {
- warnLog("Unable to initialize the provider", e);
+ warnLog("Unable to initialize the provider due to exception", e);
+ initializationSuccess = false;
+ initializationFailureReason = "onInitialize() threw exception:" + e.getMessage();
+ }
+
+ if (!initializationSuccess) {
currentState = currentState.newState(PROVIDER_STATE_PERM_FAILED, null, null,
- "Failed to initialize: " + e.getMessage());
+ "Failed to initialize: " + initializationFailureReason);
setCurrentState(currentState, true);
}
}
@@ -429,9 +470,12 @@ abstract class LocationTimeZoneProvider implements Dumpable {
/**
* Implemented by subclasses to do work during {@link #initialize}.
+ *
+ * @return returns {@code true} on success, {@code false} if the provider should be considered
+ * "permanently failed" / disabled
*/
@GuardedBy("mSharedLock")
- abstract void onInitialize();
+ abstract boolean onInitialize();
/**
* Destroys the provider. Called after the provider is stopped. This instance will not be called
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
index a9b9884e0074..ed7ea00ec8f5 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
@@ -35,6 +35,10 @@ import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
+import android.app.time.DetectorStatusTypes;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus;
import android.service.timezone.TimeZoneProviderEvent;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;
@@ -44,6 +48,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.Dumpable;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.ReferenceWithHistory;
import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue;
@@ -83,8 +88,7 @@ import java.util.Objects;
* <p>All incoming calls except for {@link
* LocationTimeZoneProviderController#dump(android.util.IndentingPrintWriter, String[])} must be
* made on the {@link android.os.Handler} thread of the {@link ThreadingDomain} passed to {@link
- * #LocationTimeZoneProviderController(ThreadingDomain, LocationTimeZoneProvider,
- * LocationTimeZoneProvider)}.
+ * #LocationTimeZoneProviderController}.
*
* <p>Provider / controller integration notes:
*
@@ -172,10 +176,10 @@ class LocationTimeZoneProviderController implements Dumpable {
@GuardedBy("mSharedLock")
private final ReferenceWithHistory<@State String> mState = new ReferenceWithHistory<>(10);
- /** Contains the last suggestion actually made, if there is one. */
+ /** Contains the last event reported, if there is one. */
@GuardedBy("mSharedLock")
@Nullable
- private GeolocationTimeZoneSuggestion mLastSuggestion;
+ private LocationAlgorithmEvent mLastEvent;
LocationTimeZoneProviderController(@NonNull ThreadingDomain threadingDomain,
@NonNull MetricsLogger metricsLogger,
@@ -213,7 +217,7 @@ class LocationTimeZoneProviderController implements Dumpable {
setState(STATE_PROVIDERS_INITIALIZING);
mPrimaryProvider.initialize(providerListener);
mSecondaryProvider.initialize(providerListener);
- setState(STATE_STOPPED);
+ setStateAndReportStatusOnlyEvent(STATE_STOPPED, "initialize()");
alterProvidersStartedStateIfRequired(
null /* oldConfiguration */, mCurrentUserConfiguration);
@@ -273,13 +277,51 @@ class LocationTimeZoneProviderController implements Dumpable {
// Enter destroyed state.
mPrimaryProvider.destroy();
mSecondaryProvider.destroy();
- setState(STATE_DESTROYED);
+ setStateAndReportStatusOnlyEvent(STATE_DESTROYED, "destroy()");
}
}
/**
- * Updates {@link #mState} if needed, and performs all the record-keeping / callbacks associated
- * with state changes.
+ * Sets the state and reports an event containing the algorithm status and a {@code null}
+ * suggestion.
+ */
+ @GuardedBy("mSharedLock")
+ private void setStateAndReportStatusOnlyEvent(@State String state, @NonNull String reason) {
+ setState(state);
+
+ final GeolocationTimeZoneSuggestion suggestion = null;
+ LocationAlgorithmEvent event =
+ new LocationAlgorithmEvent(generateCurrentAlgorithmStatus(), suggestion);
+ event.addDebugInfo(reason);
+ reportEvent(event);
+ }
+
+ /**
+ * Reports an event containing the algorithm status and the supplied suggestion.
+ */
+ @GuardedBy("mSharedLock")
+ private void reportSuggestionEvent(
+ @NonNull GeolocationTimeZoneSuggestion suggestion, @NonNull String reason) {
+ LocationTimeZoneAlgorithmStatus algorithmStatus = generateCurrentAlgorithmStatus();
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ algorithmStatus, suggestion);
+ event.addDebugInfo(reason);
+ reportEvent(event);
+ }
+
+ /**
+ * Sends an event immediately. This method updates {@link #mLastEvent}.
+ */
+ @GuardedBy("mSharedLock")
+ private void reportEvent(@NonNull LocationAlgorithmEvent event) {
+ debugLog("makeSuggestion: suggestion=" + event);
+ mCallback.sendEvent(event);
+ mLastEvent = event;
+ }
+
+ /**
+ * Updates the state if needed. This includes setting {@link #mState} and performing all the
+ * record-keeping / callbacks associated with state changes.
*/
@GuardedBy("mSharedLock")
private void setState(@State String state) {
@@ -300,17 +342,7 @@ class LocationTimeZoneProviderController implements Dumpable {
// By definition, if both providers are stopped, the controller is uncertain.
cancelUncertaintyTimeout();
- // If a previous "certain" suggestion has been made, then a new "uncertain"
- // suggestion must now be made to indicate the controller {does not / no longer has}
- // an opinion and will not be sending further updates (until at least the providers are
- // re-started).
- if (Objects.equals(mState.get(), STATE_CERTAIN)) {
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- mEnvironment.elapsedRealtimeMillis(),
- "Withdraw previous suggestion, providers are stopping: " + reason);
- makeSuggestion(suggestion, STATE_UNCERTAIN);
- }
- setState(STATE_STOPPED);
+ setStateAndReportStatusOnlyEvent(STATE_STOPPED, "Providers stopped: " + reason);
}
@GuardedBy("mSharedLock")
@@ -381,7 +413,7 @@ class LocationTimeZoneProviderController implements Dumpable {
// timeout started when the primary entered {started uncertain} should be cancelled.
if (newIsGeoDetectionExecutionEnabled) {
- setState(STATE_INITIALIZING);
+ setStateAndReportStatusOnlyEvent(STATE_INITIALIZING, "initializing()");
// Try to start the primary provider.
tryStartProvider(mPrimaryProvider, newConfiguration);
@@ -397,13 +429,11 @@ class LocationTimeZoneProviderController implements Dumpable {
ProviderState newSecondaryState = mSecondaryProvider.getCurrentState();
if (!newSecondaryState.isStarted()) {
// If both providers are {perm failed} then the controller immediately
- // reports uncertain.
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- mEnvironment.elapsedRealtimeMillis(),
- "Providers are failed:"
- + " primary=" + mPrimaryProvider.getCurrentState()
- + " secondary=" + mPrimaryProvider.getCurrentState());
- makeSuggestion(suggestion, STATE_FAILED);
+ // reports the failure.
+ String reason = "Providers are failed:"
+ + " primary=" + mPrimaryProvider.getCurrentState()
+ + " secondary=" + mPrimaryProvider.getCurrentState();
+ setStateAndReportStatusOnlyEvent(STATE_FAILED, reason);
}
}
} else {
@@ -537,12 +567,10 @@ class LocationTimeZoneProviderController implements Dumpable {
// If both providers are now terminated, then a suggestion must be sent informing the
// time zone detector that there are no further updates coming in the future.
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- mEnvironment.elapsedRealtimeMillis(),
- "Both providers are terminated:"
- + " primary=" + primaryCurrentState.provider
- + ", secondary=" + secondaryCurrentState.provider);
- makeSuggestion(suggestion, STATE_FAILED);
+ String reason = "Both providers are terminated:"
+ + " primary=" + primaryCurrentState.provider
+ + ", secondary=" + secondaryCurrentState.provider;
+ setStateAndReportStatusOnlyEvent(STATE_FAILED, reason);
}
}
@@ -615,6 +643,9 @@ class LocationTimeZoneProviderController implements Dumpable {
TimeZoneProviderSuggestion providerSuggestion = providerEvent.getSuggestion();
+ // Set the current state so it is correct when the suggestion event is created.
+ setState(STATE_CERTAIN);
+
// For the suggestion's effectiveFromElapsedMillis, use the time embedded in the provider's
// suggestion (which indicates the time when the provider detected the location used to
// establish the time zone).
@@ -623,15 +654,13 @@ class LocationTimeZoneProviderController implements Dumpable {
// this would hinder the ability for the time_zone_detector to judge which suggestions are
// based on newer information when comparing suggestions between different sources.
long effectiveFromElapsedMillis = providerSuggestion.getElapsedRealtimeMillis();
- GeolocationTimeZoneSuggestion geoSuggestion =
+ GeolocationTimeZoneSuggestion suggestion =
GeolocationTimeZoneSuggestion.createCertainSuggestion(
effectiveFromElapsedMillis, providerSuggestion.getTimeZoneIds());
-
- String debugInfo = "Event received provider=" + provider
+ String debugInfo = "Provider event received: provider=" + provider
+ ", providerEvent=" + providerEvent
+ ", suggestionCreationTime=" + mEnvironment.elapsedRealtimeMillis();
- geoSuggestion.addDebugInfo(debugInfo);
- makeSuggestion(geoSuggestion, STATE_CERTAIN);
+ reportSuggestionEvent(suggestion, debugInfo);
}
@Override
@@ -647,7 +676,7 @@ class LocationTimeZoneProviderController implements Dumpable {
+ mEnvironment.getProviderInitializationTimeoutFuzz());
ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay());
ipw.println("mState=" + mState.get());
- ipw.println("mLastSuggestion=" + mLastSuggestion);
+ ipw.println("mLastEvent=" + mLastEvent);
ipw.println("State history:");
ipw.increaseIndent(); // level 2
@@ -668,19 +697,6 @@ class LocationTimeZoneProviderController implements Dumpable {
}
}
- /**
- * Sends an immediate suggestion and enters a new state if needed. This method updates
- * mLastSuggestion and changes mStateEnum / reports the new state for metrics.
- */
- @GuardedBy("mSharedLock")
- private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion,
- @State String newState) {
- debugLog("makeSuggestion: suggestion=" + suggestion);
- mCallback.suggest(suggestion);
- mLastSuggestion = suggestion;
- setState(newState);
- }
-
/** Clears the uncertainty timeout. */
@GuardedBy("mSharedLock")
private void cancelUncertaintyTimeout() {
@@ -688,18 +704,16 @@ class LocationTimeZoneProviderController implements Dumpable {
}
/**
- * Called when a provider has become "uncertain" about the time zone.
+ * Called when a provider has reported it is "uncertain" about the time zone.
*
* <p>A provider is expected to report its uncertainty as soon as it becomes uncertain, as
* this enables the most flexibility for the controller to start other providers when there are
- * multiple ones available. The controller is therefore responsible for deciding when to make a
- * "uncertain" suggestion to the downstream time zone detector.
+ * multiple ones available. The controller is therefore responsible for deciding when to pass
+ * the "uncertain" suggestion to the downstream time zone detector.
*
* <p>This method schedules an "uncertainty" timeout (if one isn't already scheduled) to be
* triggered later if nothing else preempts it. It can be preempted if the provider becomes
- * certain (or does anything else that calls {@link
- * #makeSuggestion(GeolocationTimeZoneSuggestion, String)}) within {@link
- * Environment#getUncertaintyDelay()}. Preemption causes the scheduled
+ * certain within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled
* "uncertainty" timeout to be cancelled. If the provider repeatedly sends uncertainty events
* within the uncertainty delay period, those events are effectively ignored (i.e. the timeout
* is not reset each time).
@@ -741,6 +755,8 @@ class LocationTimeZoneProviderController implements Dumpable {
synchronized (mSharedLock) {
long afterUncertaintyTimeoutElapsedMillis = mEnvironment.elapsedRealtimeMillis();
+ setState(STATE_UNCERTAIN);
+
// For the effectiveFromElapsedMillis suggestion property, use the
// uncertaintyStartedElapsedMillis. This is the time when the provider first reported
// uncertainty, i.e. before the uncertainty timeout.
@@ -749,30 +765,65 @@ class LocationTimeZoneProviderController implements Dumpable {
// the location_time_zone_manager finally confirms that the time zone was uncertain,
// but the suggestion property allows the information to be back-dated, which should
// help when comparing suggestions from different sources.
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- uncertaintyStartedElapsedMillis,
- "Uncertainty timeout triggered for " + provider.getName() + ":"
- + " primary=" + mPrimaryProvider
- + ", secondary=" + mSecondaryProvider
- + ", uncertaintyStarted="
- + Duration.ofMillis(uncertaintyStartedElapsedMillis)
- + ", afterUncertaintyTimeout="
- + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis)
- + ", uncertaintyDelay=" + uncertaintyDelay
- );
- makeSuggestion(suggestion, STATE_UNCERTAIN);
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+ uncertaintyStartedElapsedMillis);
+ String debugInfo = "Uncertainty timeout triggered for " + provider.getName() + ":"
+ + " primary=" + mPrimaryProvider
+ + ", secondary=" + mSecondaryProvider
+ + ", uncertaintyStarted="
+ + Duration.ofMillis(uncertaintyStartedElapsedMillis)
+ + ", afterUncertaintyTimeout="
+ + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis)
+ + ", uncertaintyDelay=" + uncertaintyDelay;
+ reportSuggestionEvent(suggestion, debugInfo);
}
}
+ @GuardedBy("mSharedLock")
@NonNull
- private static GeolocationTimeZoneSuggestion createUncertainSuggestion(
- @ElapsedRealtimeLong long effectiveFromElapsedMillis,
- @NonNull String reason) {
- GeolocationTimeZoneSuggestion suggestion =
- GeolocationTimeZoneSuggestion.createUncertainSuggestion(
- effectiveFromElapsedMillis);
- suggestion.addDebugInfo(reason);
- return suggestion;
+ private LocationTimeZoneAlgorithmStatus generateCurrentAlgorithmStatus() {
+ @State String controllerState = mState.get();
+ ProviderState primaryProviderState = mPrimaryProvider.getCurrentState();
+ ProviderState secondaryProviderState = mSecondaryProvider.getCurrentState();
+ return createAlgorithmStatus(controllerState, primaryProviderState, secondaryProviderState);
+ }
+
+ @NonNull
+ private static LocationTimeZoneAlgorithmStatus createAlgorithmStatus(
+ @NonNull @State String controllerState,
+ @NonNull ProviderState primaryProviderState,
+ @NonNull ProviderState secondaryProviderState) {
+
+ @DetectionAlgorithmStatus int algorithmStatus =
+ mapControllerStateToDetectionAlgorithmStatus(controllerState);
+ @ProviderStatus int primaryProviderStatus = primaryProviderState.getProviderStatus();
+ @ProviderStatus int secondaryProviderStatus = secondaryProviderState.getProviderStatus();
+
+ // Neither provider is running. The algorithm is not running.
+ return new LocationTimeZoneAlgorithmStatus(algorithmStatus,
+ primaryProviderStatus, primaryProviderState.getReportedStatus(),
+ secondaryProviderStatus, secondaryProviderState.getReportedStatus());
+ }
+
+ /**
+ * Maps the internal state enum value to one of the status values exposed to the layers above.
+ */
+ private static @DetectionAlgorithmStatus int mapControllerStateToDetectionAlgorithmStatus(
+ @NonNull @State String controllerState) {
+ switch (controllerState) {
+ case STATE_INITIALIZING:
+ case STATE_PROVIDERS_INITIALIZING:
+ case STATE_CERTAIN:
+ case STATE_UNCERTAIN:
+ return DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+ case STATE_STOPPED:
+ case STATE_DESTROYED:
+ case STATE_FAILED:
+ case STATE_UNKNOWN:
+ default:
+ return DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+ }
}
/**
@@ -798,8 +849,8 @@ class LocationTimeZoneProviderController implements Dumpable {
synchronized (mSharedLock) {
LocationTimeZoneManagerServiceState.Builder builder =
new LocationTimeZoneManagerServiceState.Builder();
- if (mLastSuggestion != null) {
- builder.setLastSuggestion(mLastSuggestion);
+ if (mLastEvent != null) {
+ builder.setLastEvent(mLastEvent);
}
builder.setControllerState(mState.get())
.setStateChanges(mRecordedStates)
@@ -867,17 +918,15 @@ class LocationTimeZoneProviderController implements Dumpable {
abstract static class Callback {
@NonNull protected final ThreadingDomain mThreadingDomain;
- @NonNull protected final Object mSharedLock;
Callback(@NonNull ThreadingDomain threadingDomain) {
mThreadingDomain = Objects.requireNonNull(threadingDomain);
- mSharedLock = threadingDomain.getLockObject();
}
/**
* Suggests the latest time zone state for the device.
*/
- abstract void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion);
+ abstract void sendEvent(@NonNull LocationAlgorithmEvent event);
}
/**
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
index 0c751aaa62c7..7eb7e01b539a 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
@@ -19,7 +19,7 @@ package com.android.server.timezonedetector.location;
import android.annotation.NonNull;
import com.android.server.LocalServices;
-import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.TimeZoneDetectorInternal;
/**
@@ -34,11 +34,11 @@ class LocationTimeZoneProviderControllerCallbackImpl
}
@Override
- void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion) {
+ void sendEvent(@NonNull LocationAlgorithmEvent event) {
mThreadingDomain.assertCurrentThread();
TimeZoneDetectorInternal timeZoneDetector =
LocalServices.getService(TimeZoneDetectorInternal.class);
- timeZoneDetector.suggestGeolocationTimeZone(suggestion);
+ timeZoneDetector.handleLocationAlgorithmEvent(event);
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java
deleted file mode 100644
index 9cb1813df6db..000000000000
--- a/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2020 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.timezonedetector.location;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.SystemClock;
-import android.service.timezone.TimeZoneProviderEvent;
-import android.util.IndentingPrintWriter;
-
-/**
- * A {@link LocationTimeZoneProviderProxy} that provides minimal responses needed for the {@link
- * BinderLocationTimeZoneProvider} to operate correctly when there is no "real" provider
- * configured / enabled. This can be used during development / testing, or in a production build
- * when the platform supports more providers than are needed for an Android deployment.
- *
- * <p>For example, if the {@link LocationTimeZoneProviderController} supports a primary
- * and a secondary {@link LocationTimeZoneProvider}, but only a primary is configured, the secondary
- * config will be left null and the {@link LocationTimeZoneProviderProxy} implementation will be
- * defaulted to a {@link NullLocationTimeZoneProviderProxy}. The {@link
- * NullLocationTimeZoneProviderProxy} sends a "permanent failure" event immediately after being
- * started for the first time, which ensures the {@link LocationTimeZoneProviderController} won't
- * expect any further {@link TimeZoneProviderEvent}s to come from it, and won't attempt to use it
- * again.
- */
-class NullLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
-
- /** Creates the instance. */
- NullLocationTimeZoneProviderProxy(
- @NonNull Context context, @NonNull ThreadingDomain threadingDomain) {
- super(context, threadingDomain);
- }
-
- @Override
- void onInitialize() {
- // No-op
- }
-
- @Override
- void onDestroy() {
- // No-op
- }
-
- @Override
- void setRequest(@NonNull TimeZoneProviderRequest request) {
- if (request.sendUpdates()) {
- TimeZoneProviderEvent event = TimeZoneProviderEvent.createPermanentFailureEvent(
- SystemClock.elapsedRealtime(), "Provider is disabled");
- handleTimeZoneProviderEvent(event);
- }
- }
-
- @Override
- public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
- synchronized (mSharedLock) {
- ipw.println("{NullLocationTimeZoneProviderProxy}");
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 51d4bd220bc1..b290bec8c4e0 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1588,6 +1588,11 @@ class Task extends TaskFragment {
removeChild(r, reason);
});
} else {
+ // Finish or destroy apps from the bottom to ensure that all the other activity have
+ // been finished and the top task in another task gets resumed when a top activity is
+ // removed. Otherwise, shell transitions wouldn't run because there would be no event
+ // that sets the transition ready.
+ final boolean traverseTopToBottom = !mTransitionController.isShellTransitionsEnabled();
forAllActivities((r) -> {
if (r.finishing || (excludingTaskOverlay && r.isTaskOverlay())) {
return;
@@ -1601,7 +1606,7 @@ class Task extends TaskFragment {
} else {
r.destroyIfPossible(reason);
}
- });
+ }, traverseTopToBottom);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 89cbf5324ed4..c58e8d500eb2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -23,6 +23,7 @@ import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY;
import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
@@ -46,6 +47,7 @@ import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
+import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_APP_STANDBY;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_IDS;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -330,6 +332,7 @@ import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.DebugUtils;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -671,6 +674,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
private @interface CopyAccountStatus {}
/**
+ * Mapping of {@link android.app.admin.DevicePolicyManager.ApplicationExemptionConstants} to
+ * corresponding app-ops.
+ */
+ private static final Map<Integer, String> APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS =
+ new ArrayMap<>();
+ static {
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.put(
+ EXEMPT_FROM_APP_STANDBY, OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY);
+ }
+
+ /**
* Admin apps targeting Android S+ may not use
* {@link android.app.admin.DevicePolicyManager#setPasswordQuality} to set password quality
* on the {@code DevicePolicyManager} instance obtained by calling
@@ -17016,6 +17030,88 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
});
}
+ @Override
+ public void setApplicationExemptions(String packageName, int[] exemptions) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkStringNotEmpty(packageName, "Package name cannot be empty.");
+ Objects.requireNonNull(exemptions, "Application exemptions must not be null.");
+ Preconditions.checkArgument(areApplicationExemptionsValid(exemptions),
+ "Invalid application exemption constant found in application exemptions set.");
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS));
+
+ final CallerIdentity caller = getCallerIdentity();
+ final ApplicationInfo packageInfo;
+ packageInfo = getPackageInfoWithNullCheck(packageName, caller);
+
+ for (Map.Entry<Integer, String> entry :
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) {
+ int currentMode = mInjector.getAppOpsManager().unsafeCheckOpNoThrow(
+ entry.getValue(), packageInfo.uid, packageInfo.packageName);
+ int newMode = ArrayUtils.contains(exemptions, entry.getKey())
+ ? MODE_ALLOWED : MODE_DEFAULT;
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ if (currentMode != newMode) {
+ mInjector.getAppOpsManager()
+ .setMode(entry.getValue(),
+ packageInfo.uid,
+ packageName,
+ newMode);
+ }
+ });
+ }
+ }
+
+ @Override
+ public int[] getApplicationExemptions(String packageName) {
+ if (!mHasFeature) {
+ return new int[0];
+ }
+ Preconditions.checkStringNotEmpty(packageName, "Package name cannot be empty.");
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS));
+
+ final CallerIdentity caller = getCallerIdentity();
+ final ApplicationInfo packageInfo;
+ packageInfo = getPackageInfoWithNullCheck(packageName, caller);
+
+ IntArray appliedExemptions = new IntArray(0);
+ for (Map.Entry<Integer, String> entry :
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) {
+ if (mInjector.getAppOpsManager().unsafeCheckOpNoThrow(
+ entry.getValue(), packageInfo.uid, packageInfo.packageName) == MODE_ALLOWED) {
+ appliedExemptions.add(entry.getKey());
+ }
+ }
+ return appliedExemptions.toArray();
+ }
+
+ private ApplicationInfo getPackageInfoWithNullCheck(String packageName, CallerIdentity caller) {
+ final ApplicationInfo packageInfo =
+ mInjector.getPackageManagerInternal().getApplicationInfo(
+ packageName,
+ /* flags= */ 0,
+ caller.getUid(),
+ caller.getUserId());
+ if (packageInfo == null) {
+ throw new ServiceSpecificException(
+ DevicePolicyManager.ERROR_PACKAGE_NAME_NOT_FOUND,
+ "Package name not found.");
+ }
+ return packageInfo;
+ }
+
+ private boolean areApplicationExemptionsValid(int[] exemptions) {
+ for (int exemption : exemptions) {
+ if (!APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.containsKey(exemption)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private boolean isCallingFromPackage(String packageName, int callingUid) {
return mInjector.binderWithCleanCallingIdentity(() -> {
try {
diff --git a/services/permission/Android.bp b/services/permission/Android.bp
new file mode 100644
index 000000000000..b03f17b1bef7
--- /dev/null
+++ b/services/permission/Android.bp
@@ -0,0 +1,40 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "services.permission-sources",
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.kt",
+ ],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.permission",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.permission-sources"],
+ libs: [
+ "services.core",
+ // Soong fails to automatically add this dependency because all the
+ // *.kt sources are inside a filegroup.
+ "kotlin-annotations",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+ jarjar_rules: "jarjar-rules.txt",
+ kotlincflags: [
+ "-Xjvm-default=all",
+ "-Xno-call-assertions",
+ "-Xno-param-assertions",
+ "-Xno-receiver-assertions",
+ ],
+}
diff --git a/services/permission/OWNERS b/services/permission/OWNERS
new file mode 100644
index 000000000000..6c6c9fc10d3b
--- /dev/null
+++ b/services/permission/OWNERS
@@ -0,0 +1,4 @@
+ashfall@google.com
+joecastro@google.com
+ntmyren@google.com
+zhanghai@google.com
diff --git a/services/permission/jarjar-rules.txt b/services/permission/jarjar-rules.txt
new file mode 100644
index 000000000000..34af3afabc4f
--- /dev/null
+++ b/services/permission/jarjar-rules.txt
@@ -0,0 +1 @@
+rule kotlin.** com.android.server.permission.jarjar.@0
diff --git a/services/permission/java/com/android/server/permission/ModernPermissionManagerServiceImpl.kt b/services/permission/java/com/android/server/permission/ModernPermissionManagerServiceImpl.kt
new file mode 100644
index 000000000000..21ec1593fdeb
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/ModernPermissionManagerServiceImpl.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.permission
+
+import com.android.internal.annotations.Keep
+import com.android.server.pm.permission.PermissionManagerServiceInterface
+
+/**
+ * Modern implementation of [PermissionManagerServiceInterface].
+ */
+@Keep
+class ModernPermissionManagerServiceImpl
diff --git a/services/proguard_permission.flags b/services/proguard_permission.flags
new file mode 100644
index 000000000000..15edc61c33b2
--- /dev/null
+++ b/services/proguard_permission.flags
@@ -0,0 +1,9 @@
+# Only shrink services.permission classes.
+# Note that while more aggressive services shrinking is enabled by default (see proguard.flags), for
+# cases where that's not yet possible, we still need to shrink the permission package to prune out
+# unused Kotlin stdlib dependencies.
+-keep class !com.android.server.permission.** { *; }
+
+# CoverageService guards optional jacoco class references with a runtime guard, so we can safely
+# suppress build-time warnings.
+-dontwarn org.jacoco.agent.rt.*
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 0f7c0d73ad08..2f6b07bfb6f7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -466,7 +466,7 @@ public class BroadcastQueueModernImplTest {
@Test
public void testRunnableAt_Cached_Interactive() {
final BroadcastOptions options = BroadcastOptions.makeBasic();
- options.setInteractiveBroadcast(true);
+ options.setInteractive(true);
doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_INTERACTIVE);
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 54baf18da92a..82c340145f8e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -849,4 +849,53 @@ public class HdmiCecLocalDeviceTvTest {
verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
anyInt());
}
+
+ @Test
+ public void tvSendRequestArcTerminationOnSleep() {
+ // Emulate Audio device on port 0x2000 (supports ARC)
+
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+ mTestLooper.dispatchAll();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+ HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+
+ mNativeWrapper.onCecMessage(initiateArc);
+ mTestLooper.dispatchAll();
+
+ // Finish querying SADs
+ assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // ARC should be established after RequestSadAction is finished
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
+
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index fed8b4040aba..bcdc65c19330 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -21,12 +21,14 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.util.IndentingPrintWriter;
import java.util.ArrayList;
+import java.util.Objects;
public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
@@ -34,14 +36,17 @@ public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
new FakeServiceConfigAccessor();
private final ArrayList<StateChangeListener> mListeners = new ArrayList<>();
private TimeZoneState mTimeZoneState;
+ private TimeZoneDetectorStatus mStatus;
public FakeTimeZoneDetectorStrategy() {
mFakeServiceConfigAccessor.addConfigurationInternalChangeListener(
this::notifyChangeListeners);
}
- public void initializeConfiguration(ConfigurationInternal configuration) {
+ public void initializeConfigurationAndStatus(
+ ConfigurationInternal configuration, TimeZoneDetectorStatus status) {
mFakeServiceConfigAccessor.initializeCurrentUserConfiguration(configuration);
+ mStatus = Objects.requireNonNull(status);
}
@Override
@@ -57,6 +62,7 @@ public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
assertEquals("Multi-user testing not supported",
configurationInternal.getUserId(), userId);
return new TimeZoneCapabilitiesAndConfig(
+ mStatus,
configurationInternal.asCapabilities(bypassUserPolicyChecks),
configurationInternal.asConfiguration());
}
@@ -90,7 +96,7 @@ public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
}
@Override
- public void suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion timeZoneSuggestion) {
+ public void handleLocationAlgorithmEvent(LocationAlgorithmEvent locationAlgorithmEvent) {
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
index 0f667b3a690b..602842addff2 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
@@ -16,13 +16,8 @@
package com.android.server.timezonedetector;
-import static com.android.server.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
-
-import android.os.ShellCommand;
import org.junit.Test;
@@ -49,11 +44,6 @@ public class GeolocationTimeZoneSuggestionTest {
assertEquals(certain1v1, certain1v2);
assertEquals(certain1v2, certain1v1);
- // DebugInfo must not be considered in equals().
- certain1v1.addDebugInfo("Debug info 1");
- certain1v2.addDebugInfo("Debug info 2");
- assertEquals(certain1v1, certain1v2);
-
long time2 = 2222L;
GeolocationTimeZoneSuggestion certain2 =
GeolocationTimeZoneSuggestion.createCertainSuggestion(time2, ARBITRARY_ZONE_IDS1);
@@ -71,40 +61,4 @@ public class GeolocationTimeZoneSuggestionTest {
assertNotEquals(certain1v1, certain3);
assertNotEquals(certain3, certain1v1);
}
-
- @Test(expected = IllegalArgumentException.class)
- public void testParseCommandLineArg_noZoneIdsArg() {
- ShellCommand testShellCommand =
- createShellCommandWithArgsAndOptions(Collections.emptyList());
- GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
- }
-
- @Test
- public void testParseCommandLineArg_zoneIdsUncertain() {
- ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
- "--zone_ids UNCERTAIN");
- assertNull(GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand)
- .getZoneIds());
- }
-
- @Test
- public void testParseCommandLineArg_zoneIdsEmpty() {
- ShellCommand testShellCommand = createShellCommandWithArgsAndOptions("--zone_ids EMPTY");
- assertEquals(Collections.emptyList(),
- GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand).getZoneIds());
- }
-
- @Test
- public void testParseCommandLineArg_zoneIdsPresent() {
- ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
- "--zone_ids Europe/London,Europe/Paris");
- assertEquals(Arrays.asList("Europe/London", "Europe/Paris"),
- GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand).getZoneIds());
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testParseCommandLineArg_unknownArgument() {
- ShellCommand testShellCommand = createShellCommandWithArgsAndOptions("--bad_arg 0");
- GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java
new file mode 100644
index 000000000000..4c14014405f4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 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.timezonedetector;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
+
+import static com.android.server.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.os.ShellCommand;
+import android.service.timezone.TimeZoneProviderStatus;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+public class LocationAlgorithmEventTest {
+
+ public static final TimeZoneProviderStatus ARBITRARY_PROVIDER_STATUS =
+ new TimeZoneProviderStatus.Builder()
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+ .build();
+
+ public static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_ALGORITHM_STATUS =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+
+ @Test
+ public void testEquals() {
+ GeolocationTimeZoneSuggestion suggestion1 =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L);
+ LocationTimeZoneAlgorithmStatus status1 = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null, PROVIDER_STATUS_NOT_PRESENT, null);
+ LocationAlgorithmEvent event1v1 = new LocationAlgorithmEvent(status1, suggestion1);
+ assertEqualsAndHashCode(event1v1, event1v1);
+
+ LocationAlgorithmEvent event1v2 = new LocationAlgorithmEvent(status1, suggestion1);
+ assertEqualsAndHashCode(event1v1, event1v2);
+
+ GeolocationTimeZoneSuggestion suggestion2 =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(2222L);
+ LocationAlgorithmEvent event2 = new LocationAlgorithmEvent(status1, suggestion2);
+ assertNotEquals(event1v1, event2);
+
+ LocationTimeZoneAlgorithmStatus status2 = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null, PROVIDER_STATUS_NOT_READY, null);
+ LocationAlgorithmEvent event3 = new LocationAlgorithmEvent(status2, suggestion1);
+ assertNotEquals(event1v1, event3);
+
+ // DebugInfo must not be considered in equals().
+ event1v1.addDebugInfo("Debug info 1");
+ event1v2.addDebugInfo("Debug info 2");
+ assertEquals(event1v1, event1v2);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noStatus() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L);
+ ShellCommand testShellCommand =
+ createShellCommandWithArgsAndOptions(
+ Arrays.asList("--suggestion", suggestion.toString()));
+
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ }
+
+ @Test
+ public void testParseCommandLineArg_noSuggestion() {
+ GeolocationTimeZoneSuggestion suggestion = null;
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString()));
+
+ assertEquals(event, LocationAlgorithmEvent.parseCommandLineArg(testShellCommand));
+ }
+
+ @Test
+ public void testParseCommandLineArg_suggestionUncertain() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L);
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+ "--suggestion", "UNCERTAIN"));
+
+ LocationAlgorithmEvent parsedEvent =
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus());
+ assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds());
+ }
+
+ @Test
+ public void testParseCommandLineArg_suggestionEmpty() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ 1111L, Collections.emptyList());
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+ "--suggestion", "EMPTY"));
+
+ LocationAlgorithmEvent parsedEvent =
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus());
+ assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds());
+ }
+
+ @Test
+ public void testParseCommandLineArg_suggestionPresent() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ 1111L, Arrays.asList("Europe/London", "Europe/Paris"));
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+ "--suggestion", "Europe/London,Europe/Paris"));
+
+ LocationAlgorithmEvent parsedEvent =
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus());
+ assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_unknownArgument() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ 1111L, Arrays.asList("Europe/London", "Europe/Paris"));
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+ "--suggestion", "Europe/London,Europe/Paris", "--bad_arg"));
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ }
+
+ private static void assertEqualsAndHashCode(Object one, Object two) {
+ assertEquals(one, two);
+ assertEquals(two, one);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
index 223c53233065..ea801e887c4c 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
@@ -16,6 +16,10 @@
package com.android.server.timezonedetector;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+
import static com.android.server.timezonedetector.MetricsTimeZoneDetectorState.DETECTION_MODE_GEO;
import static org.junit.Assert.assertEquals;
@@ -23,6 +27,7 @@ import static org.junit.Assert.assertNull;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.UserIdInt;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -31,6 +36,7 @@ import com.android.server.timezonedetector.MetricsTimeZoneDetectorState.MetricsT
import org.junit.Test;
import java.util.Arrays;
+import java.util.List;
import java.util.function.Function;
/** Tests for {@link MetricsTimeZoneDetectorState}. */
@@ -38,6 +44,9 @@ public class MetricsTimeZoneDetectorStateTest {
private static final @UserIdInt int ARBITRARY_USER_ID = 1;
private static final @ElapsedRealtimeLong long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
+ private static final LocationTimeZoneAlgorithmStatus ARBITRARY_CERTAIN_STATUS =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
private static final String DEVICE_TIME_ZONE_ID = "DeviceTimeZoneId";
private static final ManualTimeZoneSuggestion MANUAL_TIME_ZONE_SUGGESTION =
@@ -50,11 +59,14 @@ public class MetricsTimeZoneDetectorStateTest {
.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE)
.build();
- private static final GeolocationTimeZoneSuggestion GEOLOCATION_TIME_ZONE_SUGGESTION =
+ public static final GeolocationTimeZoneSuggestion GEOLOCATION_SUGGESTION_CERTAIN =
GeolocationTimeZoneSuggestion.createCertainSuggestion(
ARBITRARY_ELAPSED_REALTIME_MILLIS,
Arrays.asList("GeoTimeZoneId1", "GeoTimeZoneId2"));
+ private static final LocationAlgorithmEvent LOCATION_ALGORITHM_EVENT =
+ new LocationAlgorithmEvent(ARBITRARY_CERTAIN_STATUS, GEOLOCATION_SUGGESTION_CERTAIN);
+
private final OrdinalGenerator<String> mOrdinalGenerator =
new OrdinalGenerator<>(Function.identity());
@@ -68,7 +80,7 @@ public class MetricsTimeZoneDetectorStateTest {
MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
- TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+ TELEPHONY_TIME_ZONE_SUGGESTION, LOCATION_ALGORITHM_EVENT);
// Assert the content.
assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
@@ -88,9 +100,10 @@ public class MetricsTimeZoneDetectorStateTest {
assertEquals(expectedTelephonySuggestion,
metricsTimeZoneDetectorState.getLatestTelephonySuggestion());
+ List<String> expectedZoneIds = LOCATION_ALGORITHM_EVENT.getSuggestion().getZoneIds();
MetricsTimeZoneSuggestion expectedGeoSuggestion =
MetricsTimeZoneSuggestion.createCertain(
- GEOLOCATION_TIME_ZONE_SUGGESTION.getZoneIds().toArray(new String[0]),
+ expectedZoneIds.toArray(new String[0]),
new int[] { 3, 4 });
assertEquals(expectedGeoSuggestion,
metricsTimeZoneDetectorState.getLatestGeolocationSuggestion());
@@ -106,7 +119,7 @@ public class MetricsTimeZoneDetectorStateTest {
MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
- TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+ TELEPHONY_TIME_ZONE_SUGGESTION, LOCATION_ALGORITHM_EVENT);
// Assert the content.
assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
index 8909832391a4..a02c8ca001ce 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -16,14 +16,22 @@
package com.android.server.timezonedetector;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.content.Context;
import android.os.HandlerThread;
@@ -41,6 +49,15 @@ import java.util.List;
@RunWith(AndroidJUnit4.class)
public class TimeZoneDetectorInternalImplTest {
+ private static final TelephonyTimeZoneAlgorithmStatus ARBITRARY_TELEPHONY_STATUS =
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING);
+ private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_CERTAIN_STATUS =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
+ private static final TimeZoneDetectorStatus ARBITRARY_DETECTOR_STATUS =
+ new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING, ARBITRARY_TELEPHONY_STATUS,
+ ARBITRARY_LOCATION_CERTAIN_STATUS);
+
private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
private static final String ARBITRARY_ZONE_ID = "TestZoneId";
private static final List<String> ARBITRARY_ZONE_IDS = Arrays.asList(ARBITRARY_ZONE_ID);
@@ -81,7 +98,8 @@ public class TimeZoneDetectorInternalImplTest {
public void testGetCapabilitiesAndConfigForDpm() throws Exception {
final boolean autoDetectionEnabled = true;
ConfigurationInternal testConfig = createConfigurationInternal(autoDetectionEnabled);
- mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(testConfig);
+ TimeZoneDetectorStatus testStatus = ARBITRARY_DETECTOR_STATUS;
+ mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(testConfig, testStatus);
TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeZoneDetectorInternal.getCapabilitiesAndConfigForDpm();
@@ -93,6 +111,7 @@ public class TimeZoneDetectorInternalImplTest {
TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
new TimeZoneCapabilitiesAndConfig(
+ testStatus,
testConfig.asCapabilities(expectedBypassUserPolicyChecks),
testConfig.asConfiguration());
assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
@@ -103,7 +122,9 @@ public class TimeZoneDetectorInternalImplTest {
final boolean autoDetectionEnabled = false;
ConfigurationInternal initialConfigurationInternal =
createConfigurationInternal(autoDetectionEnabled);
- mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfigurationInternal);
+ TimeZoneDetectorStatus testStatus = ARBITRARY_DETECTOR_STATUS;
+ mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(
+ initialConfigurationInternal, testStatus);
TimeZoneConfiguration timeConfiguration = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(true)
@@ -131,13 +152,15 @@ public class TimeZoneDetectorInternalImplTest {
}
@Test
- public void testSuggestGeolocationTimeZone() throws Exception {
+ public void testHandleLocationAlgorithmEvent() throws Exception {
GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
- mTimeZoneDetectorInternal.suggestGeolocationTimeZone(timeZoneSuggestion);
+ LocationAlgorithmEvent suggestionEvent = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion);
+ mTimeZoneDetectorInternal.handleLocationAlgorithmEvent(suggestionEvent);
mTestHandler.assertTotalMessagesEnqueued(1);
mTestHandler.waitForMessagesToBeProcessed();
- verify(mFakeTimeZoneDetectorStrategySpy).suggestGeolocationTimeZone(timeZoneSuggestion);
+ verify(mFakeTimeZoneDetectorStrategySpy).handleLocationAlgorithmEvent(suggestionEvent);
}
private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() {
return new ManualTimeZoneSuggestion(ARBITRARY_ZONE_ID);
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index d8346ee4355b..d9d8053e6220 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -16,6 +16,11 @@
package com.android.server.timezonedetector;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
@@ -34,8 +39,11 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.time.ITimeZoneDetectorListener;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -59,6 +67,13 @@ import java.util.List;
@RunWith(AndroidJUnit4.class)
public class TimeZoneDetectorServiceTest {
+ private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_CERTAIN_STATUS =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
+ private static final TimeZoneDetectorStatus ARBITRARY_DETECTOR_STATUS =
+ new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING,
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING),
+ ARBITRARY_LOCATION_CERTAIN_STATUS);
private static final int ARBITRARY_USER_ID = 9999;
private static final List<String> ARBITRARY_TIME_ZONE_IDS = Arrays.asList("TestZoneId");
private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
@@ -113,7 +128,8 @@ public class TimeZoneDetectorServiceTest {
ConfigurationInternal configuration =
createConfigurationInternal(true /* autoDetectionEnabled*/);
- mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(configuration);
+ mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(configuration,
+ ARBITRARY_DETECTOR_STATUS);
TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeZoneDetectorService.getCapabilitiesAndConfig();
@@ -128,6 +144,7 @@ public class TimeZoneDetectorServiceTest {
TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
new TimeZoneCapabilitiesAndConfig(
+ ARBITRARY_DETECTOR_STATUS,
configuration.asCapabilities(expectedBypassUserPolicyChecks),
configuration.asConfiguration());
assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
@@ -161,7 +178,9 @@ public class TimeZoneDetectorServiceTest {
public void testListenerRegistrationAndCallbacks() throws Exception {
ConfigurationInternal initialConfiguration =
createConfigurationInternal(false /* autoDetectionEnabled */);
- mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfiguration);
+
+ mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(
+ initialConfiguration, ARBITRARY_DETECTOR_STATUS);
IBinder mockListenerBinder = mock(IBinder.class);
ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
@@ -231,31 +250,35 @@ public class TimeZoneDetectorServiceTest {
}
@Test
- public void testSuggestGeolocationTimeZone_withoutPermission() {
+ public void testHandleLocationAlgorithmEvent_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion);
assertThrows(SecurityException.class,
- () -> mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion));
+ () -> mTimeZoneDetectorService.handleLocationAlgorithmEvent(event));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME_ZONE), anyString());
}
@Test
- public void testSuggestGeolocationTimeZone() throws Exception {
+ public void testHandleLocationAlgorithmEvent() throws Exception {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion);
- mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion);
+ mTimeZoneDetectorService.handleLocationAlgorithmEvent(event);
mTestHandler.assertTotalMessagesEnqueued(1);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME_ZONE), anyString());
mTestHandler.waitForMessagesToBeProcessed();
- verify(mFakeTimeZoneDetectorStrategySpy).suggestGeolocationTimeZone(timeZoneSuggestion);
+ verify(mFakeTimeZoneDetectorStrategySpy).handleLocationAlgorithmEvent(event);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index f50e7fbc76bb..b991c5a30415 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -16,6 +16,12 @@
package com.android.server.timezonedetector;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY;
@@ -35,6 +41,7 @@ import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.T
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -47,8 +54,11 @@ import static org.mockito.Mockito.verify;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -189,6 +199,9 @@ public class TimeZoneDetectorStrategyImplTest {
.setGeoDetectionEnabledSetting(true)
.build();
+ private static final TelephonyTimeZoneAlgorithmStatus TELEPHONY_ALGORITHM_RUNNING_STATUS =
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING);
+
private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private FakeEnvironment mFakeEnvironment;
private HandlerThread mHandlerThread;
@@ -233,9 +246,7 @@ public class TimeZoneDetectorStrategyImplTest {
{
mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
CONFIG_AUTO_DISABLED_GEO_DISABLED);
- mTestHandler.waitForMessagesToBeProcessed();
-
- stateChangeListener.assertNotificationsReceived(0);
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
assertEquals(CONFIG_AUTO_DISABLED_GEO_DISABLED,
mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
}
@@ -244,10 +255,7 @@ public class TimeZoneDetectorStrategyImplTest {
{
mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
CONFIG_AUTO_ENABLED_GEO_ENABLED);
- mTestHandler.waitForMessagesToBeProcessed();
-
- stateChangeListener.assertNotificationsReceived(1);
- stateChangeListener.resetNotificationsReceivedCount();
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
assertEquals(CONFIG_AUTO_ENABLED_GEO_ENABLED,
mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
}
@@ -258,10 +266,7 @@ public class TimeZoneDetectorStrategyImplTest {
new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
mTimeZoneDetectorStrategy.updateConfiguration(
USER_ID, requestedChanges, bypassUserPolicyChecks);
- mTestHandler.waitForMessagesToBeProcessed();
-
- stateChangeListener.assertNotificationsReceived(1);
- stateChangeListener.resetNotificationsReceivedCount();
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
}
}
@@ -290,11 +295,9 @@ public class TimeZoneDetectorStrategyImplTest {
new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
mTimeZoneDetectorStrategy.updateConfiguration(
otherUserId, requestedChanges, bypassUserPolicyChecks);
- mTestHandler.waitForMessagesToBeProcessed();
// Only changes to the current user's config are notified.
- stateChangeListener.assertNotificationsReceived(0);
- stateChangeListener.resetNotificationsReceivedCount();
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
}
// Current user behavior: the strategy caches and returns the latest configuration.
@@ -426,9 +429,9 @@ public class TimeZoneDetectorStrategyImplTest {
QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex1ScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion,
TELEPHONY_SCORE_NONE);
- assertEquals(expectedSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertNull(mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedSlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(SLOT_INDEX2, null);
assertEquals(expectedSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -439,10 +442,10 @@ public class TimeZoneDetectorStrategyImplTest {
QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex2ScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(slotIndex2TimeZoneSuggestion,
TELEPHONY_SCORE_NONE);
- assertEquals(expectedSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedSlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedSlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedSlotIndex2ScoredSuggestion);
// SlotIndex1 should always beat slotIndex2, all other things being equal.
assertEquals(expectedSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -477,8 +480,8 @@ public class TimeZoneDetectorStrategyImplTest {
QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(
lowQualitySuggestion, testCase.expectedScore);
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -494,8 +497,8 @@ public class TimeZoneDetectorStrategyImplTest {
QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(
goodQualitySuggestion, testCase2.expectedScore);
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -511,8 +514,8 @@ public class TimeZoneDetectorStrategyImplTest {
QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(
lowQualitySuggestion2, testCase.expectedScore);
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -543,8 +546,8 @@ public class TimeZoneDetectorStrategyImplTest {
// Assert internal service state.
QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(suggestion, testCase.expectedScore);
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -560,8 +563,8 @@ public class TimeZoneDetectorStrategyImplTest {
}
// Assert internal service state.
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -570,8 +573,8 @@ public class TimeZoneDetectorStrategyImplTest {
.verifyTimeZoneNotChanged();
// Assert internal service state.
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -622,8 +625,8 @@ public class TimeZoneDetectorStrategyImplTest {
}
// Assert internal service state.
- assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion);
assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -677,10 +680,10 @@ public class TimeZoneDetectorStrategyImplTest {
}
// Assert internal service state.
- assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedEmptySlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedEmptySlotIndex2ScoredSuggestion);
assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -690,10 +693,10 @@ public class TimeZoneDetectorStrategyImplTest {
script.verifyTimeZoneNotChanged();
// Assert internal service state.
- assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedZoneSlotIndex2ScoredSuggestion);
// SlotIndex1 should always beat slotIndex2, all other things being equal.
assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -709,20 +712,20 @@ public class TimeZoneDetectorStrategyImplTest {
}
// Assert internal service state.
- assertEquals(expectedEmptySlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedEmptySlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedZoneSlotIndex2ScoredSuggestion);
assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
// Reset the state for the next loop.
script.simulateTelephonyTimeZoneSuggestion(emptySlotIndex2Suggestion)
.verifyTimeZoneNotChanged();
- assertEquals(expectedEmptySlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedEmptySlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedEmptySlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedEmptySlotIndex2ScoredSuggestion);
}
}
@@ -866,53 +869,185 @@ public class TimeZoneDetectorStrategyImplTest {
}
@Test
- public void testGeoSuggestion_uncertain() {
+ public void testLocationAlgorithmEvent_statusChangesOnly() {
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
- .resetConfigurationTracking();
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
+
+ TimeZoneDetectorStatus expectedInitialDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ LocationTimeZoneAlgorithmStatus.UNKNOWN);
+ script.verifyCachedDetectorStatus(expectedInitialDetectorStatus);
+
+ LocationTimeZoneAlgorithmStatus algorithmStatus1 = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_NOT_READY, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ LocationTimeZoneAlgorithmStatus algorithmStatus2 = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertNotEquals(algorithmStatus1, algorithmStatus2);
+
+ {
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ new LocationAlgorithmEvent(algorithmStatus1, null);
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
- GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeolocationSuggestion();
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
- script.simulateGeolocationTimeZoneSuggestion(uncertainSuggestion)
+ // Assert internal service state.
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ algorithmStatus1);
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+ // Assert internal service state.
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+ }
+
+ {
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ new LocationAlgorithmEvent(algorithmStatus2, null);
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
+
+ // Assert internal service state.
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ algorithmStatus2);
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+ // Assert internal service state.
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+ }
+ }
+
+ @Test
+ public void testLocationAlgorithmEvent_uncertain() {
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+ Script script = new Script()
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
+ .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
+
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged();
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
+
// Assert internal service state.
- assertEquals(uncertainSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ locationAlgorithmEvent.getAlgorithmStatus());
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ // Detector remains running and location algorithm is still uncertain so nothing to report.
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+ // Assert internal service state.
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
}
@Test
- public void testGeoSuggestion_noZones() {
+ public void testLocationAlgorithmEvent_noZones() {
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
- .resetConfigurationTracking();
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
+
+ LocationAlgorithmEvent locationAlgorithmEvent = createCertainLocationAlgorithmEvent();
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
- GeolocationTimeZoneSuggestion noZonesSuggestion = createCertainGeolocationSuggestion();
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
- script.simulateGeolocationTimeZoneSuggestion(noZonesSuggestion)
+ // Assert internal service state.
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ locationAlgorithmEvent.getAlgorithmStatus());
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged();
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
+
// Assert internal service state.
- assertEquals(noZonesSuggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
}
@Test
- public void testGeoSuggestion_oneZone() {
- GeolocationTimeZoneSuggestion suggestion =
- createCertainGeolocationSuggestion("Europe/London");
-
+ public void testLocationAlgorithmEvent_oneZone() {
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
- .resetConfigurationTracking();
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
+
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/London");
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(locationAlgorithmEvent);
+
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
+
+ // Assert internal service state.
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ locationAlgorithmEvent.getAlgorithmStatus());
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
- script.simulateGeolocationTimeZoneSuggestion(suggestion)
- .verifyTimeZoneChangedAndReset(suggestion);
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
// Assert internal service state.
- assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
}
/**
@@ -921,41 +1056,35 @@ public class TimeZoneDetectorStrategyImplTest {
* set to until that unambiguously can't be correct.
*/
@Test
- public void testGeoSuggestion_multiZone() {
- GeolocationTimeZoneSuggestion londonOnlySuggestion =
- createCertainGeolocationSuggestion("Europe/London");
- GeolocationTimeZoneSuggestion londonOrParisSuggestion =
- createCertainGeolocationSuggestion("Europe/Paris", "Europe/London");
- GeolocationTimeZoneSuggestion parisOnlySuggestion =
- createCertainGeolocationSuggestion("Europe/Paris");
-
+ public void testLocationAlgorithmEvent_multiZone() {
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
- script.simulateGeolocationTimeZoneSuggestion(londonOnlySuggestion)
- .verifyTimeZoneChangedAndReset(londonOnlySuggestion);
- assertEquals(londonOnlySuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ LocationAlgorithmEvent londonOnlyEvent =
+ createCertainLocationAlgorithmEvent("Europe/London");
+ script.simulateLocationAlgorithmEvent(londonOnlyEvent)
+ .verifyTimeZoneChangedAndReset(londonOnlyEvent)
+ .verifyLatestLocationAlgorithmEventReceived(londonOnlyEvent);
// Confirm bias towards the current device zone when there's multiple zones to choose from.
- script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion)
- .verifyTimeZoneNotChanged();
- assertEquals(londonOrParisSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ LocationAlgorithmEvent londonOrParisEvent =
+ createCertainLocationAlgorithmEvent("Europe/Paris", "Europe/London");
+ script.simulateLocationAlgorithmEvent(londonOrParisEvent)
+ .verifyTimeZoneNotChanged()
+ .verifyLatestLocationAlgorithmEventReceived(londonOrParisEvent);
- script.simulateGeolocationTimeZoneSuggestion(parisOnlySuggestion)
- .verifyTimeZoneChangedAndReset(parisOnlySuggestion);
- assertEquals(parisOnlySuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ LocationAlgorithmEvent parisOnlyEvent = createCertainLocationAlgorithmEvent("Europe/Paris");
+ script.simulateLocationAlgorithmEvent(parisOnlyEvent)
+ .verifyTimeZoneChangedAndReset(parisOnlyEvent)
+ .verifyLatestLocationAlgorithmEventReceived(parisOnlyEvent);
// Now the suggestion that previously left the device on Europe/London will leave the device
// on Europe/Paris.
- script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion)
- .verifyTimeZoneNotChanged();
- assertEquals(londonOrParisSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ script.simulateLocationAlgorithmEvent(londonOrParisEvent)
+ .verifyTimeZoneNotChanged()
+ .verifyLatestLocationAlgorithmEventReceived(londonOrParisEvent);
}
/**
@@ -964,8 +1093,9 @@ public class TimeZoneDetectorStrategyImplTest {
*/
@Test
public void testChangingGeoDetectionEnabled() {
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/London");
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/London");
TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
"Europe/Paris");
@@ -973,20 +1103,22 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
- .resetConfigurationTracking();
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
// Add suggestions. Nothing should happen as time zone detection is disabled.
- script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneNotChanged();
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged()
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
- assertEquals(geolocationSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ // A detector status change is considered a "state change".
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
- .verifyTimeZoneNotChanged();
+ .verifyTimeZoneNotChanged()
+ .verifyLatestTelephonySuggestionReceived(SLOT_INDEX1, telephonySuggestion);
- assertEquals(telephonySuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1).suggestion);
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
// Toggling the time zone detection enabled setting on should cause the device setting to be
// set from the telephony signal, as we've started with geolocation time zone detection
@@ -994,18 +1126,25 @@ public class TimeZoneDetectorStrategyImplTest {
script.simulateSetAutoMode(true)
.verifyTimeZoneChangedAndReset(telephonySuggestion);
+ // A configuration change is considered a "state change".
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
+
// Changing the detection to enable geo detection will cause the device tz setting to
// change to use the latest geolocation suggestion.
script.simulateSetGeoDetectionEnabled(true)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion);
+ .verifyTimeZoneChangedAndReset(locationAlgorithmEvent);
+
+ // A configuration change is considered a "state change".
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
// Changing the detection to disable geo detection should cause the device tz setting to
// change to the telephony suggestion.
script.simulateSetGeoDetectionEnabled(false)
- .verifyTimeZoneChangedAndReset(telephonySuggestion);
+ .verifyTimeZoneChangedAndReset(telephonySuggestion)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
- assertEquals(geolocationSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ // A configuration change is considered a "state change".
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
}
@Test
@@ -1039,21 +1178,20 @@ public class TimeZoneDetectorStrategyImplTest {
// Receiving an "uncertain" geolocation suggestion should have no effect.
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(true);
}
// Receiving a "certain" geolocation suggestion should disable telephony fallback mode.
{
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/London");
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/London");
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
.verifyTelephonyFallbackIsEnabled(false);
}
@@ -1076,22 +1214,22 @@ public class TimeZoneDetectorStrategyImplTest {
// Geolocation suggestions should continue to be used as normal (previous telephony
// suggestions are not used, even when the geolocation suggestion is uncertain).
{
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/Rome");
+ LocationAlgorithmEvent certainLocationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/Rome");
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(certainLocationAlgorithmEvent)
.verifyTelephonyFallbackIsEnabled(false);
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent uncertainLocationAlgorithmEvent =
+ createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false);
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
// No change needed, device will already be set to Europe/Rome.
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false);
@@ -1108,21 +1246,20 @@ public class TimeZoneDetectorStrategyImplTest {
// Make the geolocation algorithm uncertain.
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneChangedAndReset(lastTelephonySuggestion)
.verifyTelephonyFallbackIsEnabled(true);
}
// Make the geolocation algorithm certain, disabling telephony fallback.
{
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/Lisbon");
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/Lisbon");
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
.verifyTelephonyFallbackIsEnabled(false);
}
@@ -1130,10 +1267,9 @@ public class TimeZoneDetectorStrategyImplTest {
// Demonstrate what happens when geolocation is uncertain when telephony fallback is
// enabled.
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false)
.simulateEnableTelephonyFallback()
@@ -1161,10 +1297,9 @@ public class TimeZoneDetectorStrategyImplTest {
// Receiving an "uncertain" geolocation suggestion should have no effect.
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(true);
}
@@ -1172,10 +1307,9 @@ public class TimeZoneDetectorStrategyImplTest {
// Make an uncertain geolocation suggestion, there is no telephony suggestion to fall back
// to
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(true);
}
@@ -1185,17 +1319,16 @@ public class TimeZoneDetectorStrategyImplTest {
// Geolocation suggestions should continue to be used as normal (previous telephony
// suggestions are not used, even when the geolocation suggestion is uncertain).
{
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/Rome");
+ LocationAlgorithmEvent certainEvent =
+ createCertainLocationAlgorithmEvent("Europe/Rome");
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(certainEvent)
+ .verifyTimeZoneChangedAndReset(certainEvent)
.verifyTelephonyFallbackIsEnabled(false);
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent uncertainEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(uncertainEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false);
@@ -1319,15 +1452,15 @@ public class TimeZoneDetectorStrategyImplTest {
TelephonyTimeZoneSuggestion telephonySuggestion =
createTelephonySuggestion(0 /* slotIndex */, MATCH_TYPE_NETWORK_COUNTRY_ONLY,
QUALITY_SINGLE_ZONE, "Zone2");
- GeolocationTimeZoneSuggestion geolocationTimeZoneSuggestion =
- createCertainGeolocationSuggestion("Zone3", "Zone2");
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Zone3", "Zone2");
script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
.verifyTimeZoneNotChanged()
- .simulateGeolocationTimeZoneSuggestion(geolocationTimeZoneSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged();
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
- manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion,
+ manualSuggestion, telephonySuggestion, locationAlgorithmEvent,
MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL);
// Update the config and confirm that the config metrics state updates also.
@@ -1336,11 +1469,11 @@ public class TimeZoneDetectorStrategyImplTest {
.setGeoDetectionEnabledSetting(true)
.build();
- expectedDeviceTimeZoneId = geolocationTimeZoneSuggestion.getZoneIds().get(0);
+ expectedDeviceTimeZoneId = locationAlgorithmEvent.getSuggestion().getZoneIds().get(0);
script.simulateConfigurationInternalChange(expectedInternalConfig)
.verifyTimeZoneChangedAndReset(expectedDeviceTimeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
- manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion,
+ manualSuggestion, telephonySuggestion, locationAlgorithmEvent,
MetricsTimeZoneDetectorState.DETECTION_MODE_GEO);
}
@@ -1352,7 +1485,7 @@ public class TimeZoneDetectorStrategyImplTest {
ConfigurationInternal expectedInternalConfig,
String expectedDeviceTimeZoneId, ManualTimeZoneSuggestion expectedManualSuggestion,
TelephonyTimeZoneSuggestion expectedTelephonySuggestion,
- GeolocationTimeZoneSuggestion expectedGeolocationTimeZoneSuggestion,
+ LocationAlgorithmEvent expectedLocationAlgorithmEvent,
int expectedDetectionMode) {
MetricsTimeZoneDetectorState actualState = mTimeZoneDetectorStrategy.generateMetricsState();
@@ -1365,7 +1498,7 @@ public class TimeZoneDetectorStrategyImplTest {
MetricsTimeZoneDetectorState.create(
tzIdOrdinalGenerator, expectedInternalConfig, expectedDeviceTimeZoneId,
expectedManualSuggestion, expectedTelephonySuggestion,
- expectedGeolocationTimeZoneSuggestion);
+ expectedLocationAlgorithmEvent);
// Rely on MetricsTimeZoneDetectorState.equals() for time zone ID / ID ordinal comparisons.
assertEquals(expectedState, actualState);
}
@@ -1405,20 +1538,37 @@ public class TimeZoneDetectorStrategyImplTest {
return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX2).build();
}
+ private LocationAlgorithmEvent createCertainLocationAlgorithmEvent(@NonNull String... zoneIds) {
+ GeolocationTimeZoneSuggestion suggestion = createCertainGeolocationSuggestion(zoneIds);
+ LocationTimeZoneAlgorithmStatus algorithmStatus = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_IS_CERTAIN, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
+ event.addDebugInfo("Test certain event");
+ return event;
+ }
+
+ private LocationAlgorithmEvent createUncertainLocationAlgorithmEvent() {
+ GeolocationTimeZoneSuggestion suggestion = createUncertainGeolocationSuggestion();
+ LocationTimeZoneAlgorithmStatus algorithmStatus = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_IS_UNCERTAIN, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
+ event.addDebugInfo("Test uncertain event");
+ return event;
+ }
+
private GeolocationTimeZoneSuggestion createUncertainGeolocationSuggestion() {
- return GeolocationTimeZoneSuggestion.createCertainSuggestion(
- mFakeEnvironment.elapsedRealtimeMillis(), null);
+ return GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+ mFakeEnvironment.elapsedRealtimeMillis());
}
private GeolocationTimeZoneSuggestion createCertainGeolocationSuggestion(
@NonNull String... zoneIds) {
assertNotNull(zoneIds);
- GeolocationTimeZoneSuggestion suggestion =
- GeolocationTimeZoneSuggestion.createCertainSuggestion(
- mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds));
- suggestion.addDebugInfo("Test suggestion");
- return suggestion;
+ return GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds));
}
static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment {
@@ -1499,6 +1649,14 @@ public class TimeZoneDetectorStrategyImplTest {
}
}
+ private void assertStateChangeNotificationsSent(
+ TestStateChangeListener stateChangeListener, int expectedCount) {
+ // State change notifications are asynchronous, so we have to wait.
+ mTestHandler.waitForMessagesToBeProcessed();
+
+ stateChangeListener.assertNotificationsReceivedAndReset(expectedCount);
+ }
+
/**
* A "fluent" class allows reuse of code in tests: initialization, simulation and verification
* logic.
@@ -1516,6 +1674,11 @@ public class TimeZoneDetectorStrategyImplTest {
return this;
}
+ Script registerStateChangeListener(StateChangeListener stateChangeListener) {
+ mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
+ return this;
+ }
+
Script simulateIncrementClock() {
mFakeEnvironment.incrementClock();
return this;
@@ -1555,11 +1718,10 @@ public class TimeZoneDetectorStrategyImplTest {
}
/**
- * Simulates the time zone detection strategy receiving a geolocation-originated
- * suggestion.
+ * Simulates the time zone detection strategy receiving a location algorithm event.
*/
- Script simulateGeolocationTimeZoneSuggestion(GeolocationTimeZoneSuggestion suggestion) {
- mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(suggestion);
+ Script simulateLocationAlgorithmEvent(LocationAlgorithmEvent event) {
+ mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent(event);
return this;
}
@@ -1616,7 +1778,9 @@ public class TimeZoneDetectorStrategyImplTest {
return this;
}
- Script verifyTimeZoneChangedAndReset(GeolocationTimeZoneSuggestion suggestion) {
+ Script verifyTimeZoneChangedAndReset(LocationAlgorithmEvent event) {
+ GeolocationTimeZoneSuggestion suggestion = event.getSuggestion();
+ assertNotNull("Only events with suggestions can change the time zone", suggestion);
assertEquals("Only use this method with unambiguous geo suggestions",
1, suggestion.getZoneIds().size());
verifyTimeZoneChangedAndReset(
@@ -1631,6 +1795,32 @@ public class TimeZoneDetectorStrategyImplTest {
return this;
}
+ Script verifyCachedDetectorStatus(TimeZoneDetectorStatus expectedStatus) {
+ assertEquals(expectedStatus,
+ mTimeZoneDetectorStrategy.getCachedDetectorStatusForTests());
+ return this;
+ }
+
+ Script verifyLatestLocationAlgorithmEventReceived(LocationAlgorithmEvent expectedEvent) {
+ assertEquals(expectedEvent,
+ mTimeZoneDetectorStrategy.getLatestLocationAlgorithmEvent());
+ return this;
+ }
+
+ Script verifyLatestTelephonySuggestionReceived(int slotIndex,
+ TelephonyTimeZoneSuggestion expectedSuggestion) {
+ assertEquals(expectedSuggestion,
+ mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(slotIndex).suggestion);
+ return this;
+ }
+
+ Script verifyLatestQualifiedTelephonySuggestionReceived(int slotIndex,
+ QualifiedTelephonyTimeZoneSuggestion expectedQualifiedSuggestion) {
+ assertEquals(expectedQualifiedSuggestion,
+ mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(slotIndex));
+ return this;
+ }
+
Script resetConfigurationTracking() {
mFakeEnvironment.commitAllChanges();
return this;
@@ -1671,11 +1861,16 @@ public class TimeZoneDetectorStrategyImplTest {
mNotificationsReceived++;
}
- public void resetNotificationsReceivedCount() {
+ public void assertNotificationsReceivedAndReset(int expectedCount) {
+ assertNotificationsReceived(expectedCount);
+ resetNotificationsReceivedCount();
+ }
+
+ private void resetNotificationsReceivedCount() {
mNotificationsReceived = 0;
}
- public void assertNotificationsReceived(int expectedCount) {
+ private void assertNotificationsReceived(int expectedCount) {
assertEquals(expectedCount, mNotificationsReceived);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
index c18acd20e96a..7b1db953ef54 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
@@ -15,6 +15,8 @@
*/
package com.android.server.timezonedetector.location;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE;
import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE;
@@ -42,6 +44,7 @@ import static com.android.server.timezonedetector.location.TestSupport.USER2_CON
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -51,6 +54,7 @@ import static java.util.Arrays.asList;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.service.timezone.TimeZoneProviderEvent;
@@ -60,6 +64,7 @@ import android.util.IndentingPrintWriter;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.TestState;
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderMetricsLogger;
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
@@ -141,7 +146,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.setFailDuringInitialization(true);
// Initialize. After initialization the providers must be initialized and one should be
- // started.
+ // started. They should report their status change via the callback.
controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertInitialized();
@@ -154,7 +159,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout);
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -184,7 +190,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -211,7 +218,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING, STATE_FAILED);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -239,7 +247,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -262,7 +271,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -282,7 +292,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
@@ -296,7 +307,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate time passing with no provider event being received from either the primary or
@@ -311,7 +322,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Finally, the uncertainty timeout should cause the controller to make an uncertain
@@ -324,7 +335,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit();
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -345,7 +356,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
@@ -358,7 +370,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -380,7 +392,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
@@ -392,7 +405,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the primary provider. This should cause a
@@ -405,7 +418,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -427,7 +440,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
@@ -439,7 +453,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the secondary provider. This should cause a
@@ -453,7 +467,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -475,7 +489,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
@@ -488,7 +503,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -501,7 +516,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// And a third, different event should cause another suggestion.
@@ -513,7 +528,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -535,7 +550,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
@@ -547,7 +563,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the secondary provider. This should cause a
@@ -561,7 +577,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -575,7 +591,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// And a third, different event should cause another suggestion.
@@ -588,7 +604,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -610,7 +626,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
@@ -623,7 +640,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -639,7 +656,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the secondary provider. This should cause a
@@ -654,7 +671,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -670,7 +687,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate time passing. This means the uncertainty timeout should fire and the uncertain
@@ -683,7 +700,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN);
- mTestCallback.assertUncertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit(
USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -705,7 +722,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
@@ -718,7 +736,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -733,7 +751,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// And a success event from the primary provider should cause the controller to make another
@@ -747,7 +765,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -767,7 +785,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled.
@@ -778,7 +797,8 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is disabled.
@@ -788,7 +808,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -807,7 +828,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled.
@@ -818,7 +840,8 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a success event being received from the primary provider.
@@ -830,7 +853,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -843,8 +866,9 @@ public class LocationTimeZoneProviderControllerTest {
assertControllerState(controller, STATE_STOPPED);
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN, STATE_STOPPED);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -865,7 +889,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate the primary provider suggesting a time zone.
@@ -879,7 +904,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -897,9 +922,9 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfig(
PROVIDER_STATE_STARTED_INITIALIZING, USER2_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestMetricsLogger.assertStateChangesAndCommit(
- STATE_UNCERTAIN, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED, STATE_INITIALIZING);
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -920,7 +945,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a failure location event being received from the primary provider. This should
@@ -933,7 +959,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate uncertainty from the secondary.
@@ -945,7 +971,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// And a success event from the secondary provider should cause the controller to make
@@ -958,7 +984,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -971,7 +997,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
}
@@ -992,7 +1018,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a failure location event being received from the primary provider. This should
@@ -1005,7 +1032,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is disabled.
@@ -1015,7 +1042,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled.
@@ -1026,7 +1054,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -1047,7 +1076,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate an uncertain event from the primary. This will start the secondary, which will
@@ -1062,7 +1092,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate failure event from the secondary. This should just affect the secondary's state.
@@ -1074,7 +1104,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// And a success event from the primary provider should cause the controller to make
@@ -1087,7 +1117,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -1100,7 +1130,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
}
@@ -1121,7 +1151,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate an uncertain event from the primary. This will start the secondary, which will
@@ -1136,7 +1167,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate failure event from the secondary. This should just affect the secondary's state.
@@ -1148,7 +1179,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Now signal a config change so that geo detection is disabled.
@@ -1158,7 +1189,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled. Only the primary can be
@@ -1170,7 +1202,8 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -1191,7 +1224,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a failure event from the primary. This will start the secondary.
@@ -1203,7 +1237,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate failure event from the secondary.
@@ -1214,7 +1248,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_FAILED);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -1233,7 +1268,7 @@ public class LocationTimeZoneProviderControllerTest {
{
LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertEquals(STATE_INITIALIZING, state.getControllerState());
- assertNull(state.getLastSuggestion());
+ assertNull(state.getLastEvent().getSuggestion());
assertControllerRecordedStates(state,
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
assertProviderStates(state.getPrimaryProviderStates(),
@@ -1251,7 +1286,7 @@ public class LocationTimeZoneProviderControllerTest {
{
LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertEquals(STATE_INITIALIZING, state.getControllerState());
- assertNull(state.getLastSuggestion());
+ assertNull(state.getLastEvent().getSuggestion());
assertControllerRecordedStates(state);
assertProviderStates(
state.getPrimaryProviderStates(), PROVIDER_STATE_STARTED_UNCERTAIN);
@@ -1268,7 +1303,7 @@ public class LocationTimeZoneProviderControllerTest {
LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertEquals(STATE_CERTAIN, state.getControllerState());
assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
- state.getLastSuggestion().getZoneIds());
+ state.getLastEvent().getSuggestion().getZoneIds());
assertControllerRecordedStates(state, STATE_CERTAIN);
assertProviderStates(state.getPrimaryProviderStates());
assertProviderStates(
@@ -1280,7 +1315,7 @@ public class LocationTimeZoneProviderControllerTest {
LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertEquals(STATE_CERTAIN, state.getControllerState());
assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
- state.getLastSuggestion().getZoneIds());
+ state.getLastEvent().getSuggestion().getZoneIds());
assertControllerRecordedStates(state);
assertProviderStates(state.getPrimaryProviderStates());
assertProviderStates(state.getSecondaryProviderStates());
@@ -1313,7 +1348,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate the primary provider suggesting a time zone.
@@ -1327,7 +1363,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -1335,11 +1371,11 @@ public class LocationTimeZoneProviderControllerTest {
controller.destroy();
assertControllerState(controller, STATE_DESTROYED);
- mTestMetricsLogger.assertStateChangesAndCommit(
- STATE_UNCERTAIN, STATE_STOPPED, STATE_DESTROYED);
+ mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED, STATE_DESTROYED);
// Confirm that the previous suggestion was overridden.
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
mTestPrimaryLocationTimeZoneProvider.assertStateChangesAndCommit(
PROVIDER_STATE_STOPPED, PROVIDER_STATE_DESTROYED);
@@ -1517,63 +1553,101 @@ public class LocationTimeZoneProviderControllerTest {
private static class TestCallback extends LocationTimeZoneProviderController.Callback {
- private TestState<GeolocationTimeZoneSuggestion> mLatestSuggestion = new TestState<>();
+ private TestState<LocationAlgorithmEvent> mLatestEvent = new TestState<>();
TestCallback(ThreadingDomain threadingDomain) {
super(threadingDomain);
}
@Override
- void suggest(GeolocationTimeZoneSuggestion suggestion) {
- mLatestSuggestion.set(suggestion);
+ void sendEvent(LocationAlgorithmEvent event) {
+ mLatestEvent.set(event);
+ }
+
+ void assertNoEventReported() {
+ mLatestEvent.assertHasNotBeenSet();
+ }
+
+ /**
+ * Asserts one or more events have been reported, and the most recent does not contain a
+ * suggestion.
+ */
+ void assertEventWithNoSuggestionReportedAndCommit(
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus) {
+ mLatestEvent.assertHasBeenSet();
+
+ LocationAlgorithmEvent latest = mLatestEvent.getLatest();
+ assertEquals(expectedAlgorithmStatus, latest.getAlgorithmStatus().getStatus());
+ assertNull(latest.getSuggestion());
+ mLatestEvent.commitLatest();
}
- void assertCertainSuggestionMadeFromEventAndCommit(TimeZoneProviderEvent event) {
+ void assertEventWithCertainSuggestionReportedAndCommit(TimeZoneProviderEvent event) {
// Test coding error if this fails.
assertEquals(TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION, event.getType());
+ // By definition, the algorithm has to be running to report a suggestion.
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus =
+ DETECTION_ALGORITHM_STATUS_RUNNING;
TimeZoneProviderSuggestion suggestion = event.getSuggestion();
- assertSuggestionMadeAndCommit(
+ assertEventWithSuggestionReportedAndCommit(
+ expectedAlgorithmStatus,
suggestion.getElapsedRealtimeMillis(),
suggestion.getTimeZoneIds());
}
- void assertNoSuggestionMade() {
- mLatestSuggestion.assertHasNotBeenSet();
- }
-
- /** Asserts that an uncertain suggestion has been made from the supplied event. */
- void assertUncertainSuggestionMadeFromEventAndCommit(TimeZoneProviderEvent event) {
+ /**
+ * Asserts that one or more events have been reported, and the most recent contains an
+ * uncertain suggestion matching select details from the supplied provider event.
+ */
+ void assertEventWithUncertainSuggestionReportedAndCommit(TimeZoneProviderEvent event) {
// Test coding error if this fails.
assertEquals(TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN, event.getType());
- assertSuggestionMadeAndCommit(event.getCreationElapsedMillis(), null);
+ // By definition, the algorithm has to be running to report a suggestion.
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus =
+ DETECTION_ALGORITHM_STATUS_RUNNING;
+ assertEventWithSuggestionReportedAndCommit(
+ expectedAlgorithmStatus, event.getCreationElapsedMillis(), null);
}
/**
- * Asserts that an uncertain suggestion has been made.
- * Ignores the suggestion's effectiveFromElapsedMillis.
+ * Asserts that one or more events have been reported, and the most recent contains an
+ * uncertain suggestion. Ignores the suggestion's effectiveFromElapsedMillis.
*/
- void assertUncertainSuggestionMadeAndCommit() {
+ void assertEventWithUncertainSuggestionReportedAndCommit() {
+ // By definition, the algorithm has to be running to report a suggestion.
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus =
+ DETECTION_ALGORITHM_STATUS_RUNNING;
+
// An "uncertain" suggestion has null time zone IDs.
- assertSuggestionMadeAndCommit(null, null);
+ assertEventWithSuggestionReportedAndCommit(expectedAlgorithmStatus, null, null);
}
/**
- * Asserts that a suggestion has been made and some properties of that suggestion.
- * When expectedEffectiveFromElapsedMillis is null then its value isn't checked.
+ * Asserts that an event has been reported containing a suggestion and some properties of
+ * that suggestion. When expectedEffectiveFromElapsedMillis is null then its value isn't
+ * checked.
*/
- private void assertSuggestionMadeAndCommit(
+ private void assertEventWithSuggestionReportedAndCommit(
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus,
@Nullable @ElapsedRealtimeLong Long expectedEffectiveFromElapsedMillis,
@Nullable List<String> expectedZoneIds) {
- mLatestSuggestion.assertHasBeenSet();
+ mLatestEvent.assertHasBeenSet();
+
+ LocationAlgorithmEvent latestEvent = mLatestEvent.getLatest();
+ assertEquals(expectedAlgorithmStatus, latestEvent.getAlgorithmStatus().getStatus());
+
+ GeolocationTimeZoneSuggestion suggestion = latestEvent.getSuggestion();
+ assertNotNull("Latest event doesn't contain a suggestion: event=" + latestEvent,
+ suggestion);
+
if (expectedEffectiveFromElapsedMillis != null) {
- assertEquals(
- expectedEffectiveFromElapsedMillis.longValue(),
- mLatestSuggestion.getLatest().getEffectiveFromElapsedMillis());
+ assertEquals(expectedEffectiveFromElapsedMillis.longValue(),
+ suggestion.getEffectiveFromElapsedMillis());
}
- assertEquals(expectedZoneIds, mLatestSuggestion.getLatest().getZoneIds());
- mLatestSuggestion.commitLatest();
+ assertEquals(expectedZoneIds, suggestion.getZoneIds());
+ mLatestEvent.commitLatest();
}
}
@@ -1598,11 +1672,9 @@ public class LocationTimeZoneProviderControllerTest {
}
@Override
- void onInitialize() {
+ boolean onInitialize() {
mInitialized = true;
- if (mFailDuringInitialization) {
- throw new RuntimeException("Simulated initialization failure");
- }
+ return !mFailDuringInitialization;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
index 2bee7e66c43f..1ae74c679b53 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
@@ -330,8 +330,9 @@ public class LocationTimeZoneProviderTest {
}
@Override
- void onInitialize() {
+ boolean onInitialize() {
mOnInitializeCalled = true;
+ return true;
}
@Override
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index ccec67e8687e..af37ed583438 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1851,7 +1851,7 @@ public class TelecomManager {
ITelecomService service = getTelecomService();
if (service != null) {
try {
- return service.getCallStateUsingPackage(mContext.getPackageName(),
+ return service.getCallStateUsingPackage(mContext.getOpPackageName(),
mContext.getAttributionTag());
} catch (RemoteException e) {
Log.d(TAG, "RemoteException calling getCallState().", e);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 73551b9441fc..8c3ef675dabc 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -8298,16 +8298,23 @@ public class TelephonyManager {
/** Authentication type for UICC challenge is EAP AKA. See RFC 4187 for details. */
public static final int AUTHTYPE_EAP_AKA = PhoneConstants.AUTH_CONTEXT_EAP_AKA;
/**
- * Authentication type for GBA Bootstrap Challenge is GBA_BOOTSTRAP.
- * See 3GPP 33.220 Section 5.3.2.
- * @hide
+ * Authentication type for GBA Bootstrap Challenge.
+ * Pass this authentication type into the {@link #getIccAuthentication} API to perform a GBA
+ * Bootstrap challenge (BSF), with {@code data} (generated according to the procedure defined in
+ * 3GPP 33.220 Section 5.3.2 step.4) in base64 encoding.
+ * This method will return the Bootstrapping response in base64 encoding when ICC authentication
+ * is completed.
+ * Ref 3GPP 33.220 Section 5.3.2.
*/
public static final int AUTHTYPE_GBA_BOOTSTRAP = PhoneConstants.AUTH_CONTEXT_GBA_BOOTSTRAP;
/**
- * Authentication type for GBA Network Application Functions (NAF) key
- * External Challenge is AUTHTYPE_GBA_NAF_KEY_EXTERNAL.
- * See 3GPP 33.220 Section 5.3.2.
- * @hide
+ * Authentication type for GBA Network Application Functions (NAF) key External Challenge.
+ * Pass this authentication type into the {@link #getIccAuthentication} API to perform a GBA
+ * Network Applications Functions (NAF) key External challenge using the NAF_ID parameter
+ * as the {@code data} in base64 encoding.
+ * This method will return the Ks_Ext_Naf key in base64 encoding when ICC authentication
+ * is completed.
+ * Ref 3GPP 33.220 Section 5.3.2.
*/
public static final int AUTHTYPE_GBA_NAF_KEY_EXTERNAL =
PhoneConstants.AUTHTYPE_GBA_NAF_KEY_EXTERNAL;
@@ -8336,7 +8343,8 @@ public class TelephonyManager {
*
* @param appType the icc application type, like {@link #APPTYPE_USIM}
* @param authType the authentication type, any one of {@link #AUTHTYPE_EAP_AKA} or
- * {@link #AUTHTYPE_EAP_SIM}
+ * {@link #AUTHTYPE_EAP_SIM} or {@link #AUTHTYPE_GBA_BOOTSTRAP} or
+ * {@link #AUTHTYPE_GBA_NAF_KEY_EXTERNAL}
* @param data authentication challenge data, base64 encoded.
* See 3GPP TS 31.102 7.1.2 for more details.
* @return the response of authentication. This value will be null in the following cases:
@@ -8364,7 +8372,8 @@ public class TelephonyManager {
* @param subId subscription ID used for authentication
* @param appType the icc application type, like {@link #APPTYPE_USIM}
* @param authType the authentication type, any one of {@link #AUTHTYPE_EAP_AKA} or
- * {@link #AUTHTYPE_EAP_SIM}
+ * {@link #AUTHTYPE_EAP_SIM} or {@link #AUTHTYPE_GBA_BOOTSTRAP} or
+ * {@link #AUTHTYPE_GBA_NAF_KEY_EXTERNAL}
* @param data authentication challenge data, base64 encoded.
* See 3GPP TS 31.102 7.1.2 for more details.
* @return the response of authentication. This value will be null in the following cases only
@@ -9531,12 +9540,13 @@ public class TelephonyManager {
/**
* Set the allowed network types of the device and provide the reason triggering the allowed
* network change.
- * <p>Requires permission: android.Manifest.MODIFY_PHONE_STATE or
+ * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or
* that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
- * This can be called for following reasons
+ * This can be called for following reasons:
* <ol>
- * <li>Allowed network types control by USER {@link #ALLOWED_NETWORK_TYPES_REASON_USER}
+ * <li>Allowed network types control by USER
+ * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER}
* <li>Allowed network types control by carrier {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER}
* </ol>
* This API will result in allowing an intersection of allowed network types for all reasons,
@@ -9546,7 +9556,13 @@ public class TelephonyManager {
* @param allowedNetworkTypes The bitmask of allowed network type
* @throws IllegalStateException if the Telephony process is not currently available.
* @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed.
- * @throws SecurityException if the caller does not have the required privileges
+ * @throws SecurityException if the caller does not have the required privileges or if the
+ * caller tries to use one of the following security-based reasons without
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} permissions.
+ * <ol>
+ * <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}</li>
+ * <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS}</li>
+ * </ol>
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 340ee7202757..0c14dbaa5a3f 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -543,6 +543,9 @@ public interface RILConstants {
int RIL_REQUEST_SEND_ANBR_QUERY = 237;
int RIL_REQUEST_TRIGGER_EPS_FALLBACK = 238;
int RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED = 239;
+ int RIL_REQUEST_UPDATE_IMS_CALL_STATUS = 240;
+ int RIL_REQUEST_SET_N1_MODE_ENABLED = 241;
+ int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;