summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--apex/jobscheduler/framework/java/android/app/AlarmManager.java132
-rw-r--r--apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java18
-rw-r--r--apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java3
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java45
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java4
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobStore.java2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java58
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java35
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java21
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java14
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java21
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java35
-rw-r--r--core/api/current.txt138
-rw-r--r--core/api/module-lib-current.txt4
-rw-r--r--core/api/system-current.txt8
-rw-r--r--core/api/test-current.txt2
-rw-r--r--core/java/android/app/SyncNotedAppOp.java19
-rw-r--r--core/java/android/app/WallpaperManager.java7
-rw-r--r--core/java/android/content/pm/PackageInstaller.java18
-rw-r--r--core/java/android/credentials/Credential.java3
-rw-r--r--core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java115
-rw-r--r--core/java/android/inputmethodservice/RemoteInputConnection.java14
-rw-r--r--core/java/android/os/Parcelable.java2
-rw-r--r--core/java/android/provider/Settings.java7
-rw-r--r--core/java/android/service/credentials/CredentialEntry.java16
-rw-r--r--core/java/android/service/credentials/SaveEntry.java96
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java1
-rw-r--r--core/java/android/text/SegmentFinder.java147
-rw-r--r--core/java/android/util/FeatureFlagUtils.java25
-rw-r--r--core/java/android/view/HandwritingDelegateConfiguration.java74
-rw-r--r--core/java/android/view/HandwritingInitiator.java28
-rw-r--r--core/java/android/view/View.java33
-rw-r--r--core/java/android/view/inputmethod/InputConnection.java39
-rw-r--r--core/java/android/view/inputmethod/InputConnectionWrapper.java13
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java14
-rw-r--r--core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl19
-rw-r--r--core/java/android/view/inputmethod/ParcelableHandwritingGesture.java109
-rw-r--r--core/java/android/view/inputmethod/RemoteInputConnectionImpl.java90
-rw-r--r--core/java/android/view/inputmethod/TextBoundsInfo.java844
-rw-r--r--core/java/android/view/inputmethod/TextBoundsInfoResult.java137
-rw-r--r--core/java/android/window/TransitionInfo.java11
-rw-r--r--core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl34
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java141
-rw-r--r--core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java25
-rw-r--r--core/tests/coretests/src/com/android/internal/security/OWNERS3
-rw-r--r--graphics/java/android/view/PixelCopy.java294
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java2
-rw-r--r--media/java/android/media/MediaRouter2.java4
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt4
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt52
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt67
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt58
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt69
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt143
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt27
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt25
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt52
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt68
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt34
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt59
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt67
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt141
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt58
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt (renamed from packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt)2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt (renamed from packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt)2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasskeyCredentialEntryUi.kt (renamed from packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt)2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasswordCredentialEntryUi.kt (renamed from packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt)2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt (renamed from packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt)2
-rw-r--r--packages/SettingsLib/Android.bp1
-rw-r--r--packages/SettingsLib/ProfileSelector/Android.bp27
-rw-r--r--packages/SettingsLib/ProfileSelector/AndroidManifest.xml22
-rw-r--r--packages/SettingsLib/ProfileSelector/res/color-night/settingslib_tabs_indicator_color.xml (renamed from packages/SettingsLib/res/color-night-v31/settingslib_tabs_indicator_color.xml)0
-rw-r--r--packages/SettingsLib/ProfileSelector/res/color-night/settingslib_tabs_text_color.xml (renamed from packages/SettingsLib/res/color-night-v31/settingslib_tabs_text_color.xml)0
-rw-r--r--packages/SettingsLib/ProfileSelector/res/color/settingslib_tabs_indicator_color.xml (renamed from packages/SettingsLib/res/color-v31/settingslib_tabs_indicator_color.xml)0
-rw-r--r--packages/SettingsLib/ProfileSelector/res/color/settingslib_tabs_text_color.xml (renamed from packages/SettingsLib/res/color-v31/settingslib_tabs_text_color.xml)0
-rw-r--r--packages/SettingsLib/ProfileSelector/res/drawable/settingslib_tabs_background.xml (renamed from packages/SettingsLib/res/drawable-v31/settingslib_tabs_background.xml)0
-rw-r--r--packages/SettingsLib/ProfileSelector/res/drawable/settingslib_tabs_indicator_background.xml (renamed from packages/SettingsLib/res/drawable-v31/settingslib_tabs_indicator_background.xml)0
-rw-r--r--packages/SettingsLib/ProfileSelector/res/layout/tab_fragment.xml38
-rw-r--r--packages/SettingsLib/ProfileSelector/res/values/strings.xml24
-rw-r--r--packages/SettingsLib/ProfileSelector/res/values/styles.xml (renamed from packages/SettingsLib/res/values-v31/styles.xml)0
-rw-r--r--packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileSelectFragment.java118
-rw-r--r--packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileViewPagerAdapter.java43
-rw-r--r--packages/SettingsLib/SelectorWithWidgetPreference/Android.bp1
-rw-r--r--packages/SettingsLib/SettingsTheme/Android.bp1
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt9
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt8
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt12
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt6
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt10
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt6
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt6
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt10
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt10
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt11
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt15
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt10
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt10
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt10
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt49
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt6
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt15
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt21
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt182
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt4
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExt.kt35
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt129
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/qrcode/OWNERS8
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SystemUI/ktfmt_includes.txt4
-rw-r--r--packages/SystemUI/res/layout/user_switcher_fullscreen.xml118
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl5
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt)71
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt34
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java85
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt)55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java317
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt234
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java35
-rw-r--r--services/core/java/com/android/server/ServiceThread.java10
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java38
-rw-r--r--services/core/java/com/android/server/am/BroadcastLoopers.java3
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java88
-rw-r--r--services/core/java/com/android/server/am/BroadcastSkipPolicy.java234
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java5
-rw-r--r--services/core/java/com/android/server/am/ProcessErrorStateRecord.java14
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java1
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java45
-rw-r--r--services/core/java/com/android/server/pm/AppsFilterImpl.java139
-rw-r--r--services/core/java/com/android/server/pm/BackgroundDexOptService.java2
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java3
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java4
-rw-r--r--services/core/java/com/android/server/pm/RemovePackageHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/SnapshotStatistics.java117
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java5
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java9
-rw-r--r--services/tests/InputMethodSystemServerTests/Android.bp62
-rw-r--r--services/tests/InputMethodSystemServerTests/AndroidManifest.xml42
-rw-r--r--services/tests/InputMethodSystemServerTests/AndroidTest.xml39
-rw-r--r--services/tests/InputMethodSystemServerTests/OWNERS1
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java259
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java266
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java26
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java6
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java75
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java16
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java104
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java9
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java142
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java7
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java26
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java30
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java26
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java30
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java8
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java38
-rw-r--r--telephony/java/android/telephony/SmsManager.java85
-rw-r--r--telephony/java/com/android/internal/telephony/RILConstants.java9
187 files changed, 6441 insertions, 1636 deletions
diff --git a/Android.bp b/Android.bp
index 0315c12f95a1..0a1456514010 100644
--- a/Android.bp
+++ b/Android.bp
@@ -214,6 +214,7 @@ java_library {
"android.hardware.radio-V1.5-java",
"android.hardware.radio-V1.6-java",
"android.hardware.radio.data-V1-java",
+ "android.hardware.radio.ims-V1-java",
"android.hardware.radio.messaging-V1-java",
"android.hardware.radio.modem-V1-java",
"android.hardware.radio.network-V2-java",
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 5a445d476711..dade7c3d84a8 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -309,7 +309,7 @@ public class AlarmManager {
/**
* Callback method that is invoked by the system when the alarm time is reached.
*/
- public void onAlarm();
+ void onAlarm();
}
final class ListenerWrapper extends IAlarmListener.Stub implements Runnable {
@@ -453,7 +453,7 @@ public class AlarmManager {
* @see #RTC
* @see #RTC_WAKEUP
*/
- public void set(@AlarmType int type, long triggerAtMillis, PendingIntent operation) {
+ public void set(@AlarmType int type, long triggerAtMillis, @NonNull PendingIntent operation) {
setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null,
(Handler) null, null, null);
}
@@ -480,8 +480,8 @@ public class AlarmManager {
* @param targetHandler {@link Handler} on which to execute the listener's onAlarm()
* callback, or {@code null} to run that callback on the main looper.
*/
- public void set(@AlarmType int type, long triggerAtMillis, String tag, OnAlarmListener listener,
- Handler targetHandler) {
+ public void set(@AlarmType int type, long triggerAtMillis, @Nullable String tag,
+ @NonNull OnAlarmListener listener, @Nullable Handler targetHandler) {
setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, null, listener, tag,
targetHandler, null, null);
}
@@ -546,7 +546,7 @@ public class AlarmManager {
* @see Intent#EXTRA_ALARM_COUNT
*/
public void setRepeating(@AlarmType int type, long triggerAtMillis,
- long intervalMillis, PendingIntent operation) {
+ long intervalMillis, @NonNull PendingIntent operation) {
setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation,
null, null, (Handler) null, null, null);
}
@@ -602,7 +602,7 @@ public class AlarmManager {
* @see #RTC_WAKEUP
*/
public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
- PendingIntent operation) {
+ @NonNull PendingIntent operation) {
setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation,
null, null, (Handler) null, null, null);
}
@@ -625,12 +625,62 @@ public class AlarmManager {
* @see #setWindow(int, long, long, PendingIntent)
*/
public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
- String tag, OnAlarmListener listener, Handler targetHandler) {
+ @Nullable String tag, @NonNull OnAlarmListener listener,
+ @Nullable Handler targetHandler) {
setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag,
targetHandler, null, null);
}
/**
+ * Direct callback version of {@link #setWindow(int, long, long, PendingIntent)}. Rather
+ * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+ * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+ * <p>
+ * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Executor.
+ *
+ * <p>
+ * Note: Starting with API {@link Build.VERSION_CODES#S}, apps should not pass in a window of
+ * less than 10 minutes. The system will try its best to accommodate smaller windows if the
+ * alarm is supposed to fire in the near future, but there are no guarantees and the app should
+ * expect any window smaller than 10 minutes to get elongated to 10 minutes.
+ *
+ * @see #setWindow(int, long, long, PendingIntent)
+ */
+ public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
+ @Nullable String tag, @NonNull Executor executor, @NonNull OnAlarmListener listener) {
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag,
+ executor, null, null);
+ }
+
+ /**
+ * Direct callback version of {@link #setWindow(int, long, long, PendingIntent)}. Rather
+ * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+ * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+ * <p>
+ * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Executor.
+ *
+ * <p>
+ * Note: Starting with API {@link Build.VERSION_CODES#S}, apps should not pass in a window of
+ * less than 10 minutes. The system will try its best to accommodate smaller windows if the
+ * alarm is supposed to fire in the near future, but there are no guarantees and the app should
+ * expect any window smaller than 10 minutes to get elongated to 10 minutes.
+ *
+ * @see #setWindow(int, long, long, PendingIntent)
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
+ @Nullable String tag, @NonNull Executor executor, @Nullable WorkSource workSource,
+ @NonNull OnAlarmListener listener) {
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag,
+ executor, workSource, null);
+ }
+
+ /**
* Schedule an alarm that is prioritized by the system while the device is in power saving modes
* such as battery saver and device idle (doze).
*
@@ -725,7 +775,8 @@ public class AlarmManager {
* @see Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM
*/
@RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
- public void setExact(@AlarmType int type, long triggerAtMillis, PendingIntent operation) {
+ public void setExact(@AlarmType int type, long triggerAtMillis,
+ @NonNull PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, (Handler) null,
null, null);
}
@@ -756,8 +807,8 @@ public class AlarmManager {
* @see Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM
*/
@RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
- public void setExact(@AlarmType int type, long triggerAtMillis, String tag,
- OnAlarmListener listener, Handler targetHandler) {
+ public void setExact(@AlarmType int type, long triggerAtMillis, @Nullable String tag,
+ @NonNull OnAlarmListener listener, @Nullable Handler targetHandler) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, null, listener, tag,
targetHandler, null, null);
}
@@ -767,8 +818,8 @@ public class AlarmManager {
* the given time.
* @hide
*/
- public void setIdleUntil(@AlarmType int type, long triggerAtMillis, String tag,
- OnAlarmListener listener, Handler targetHandler) {
+ public void setIdleUntil(@AlarmType int type, long triggerAtMillis, @Nullable String tag,
+ @NonNull OnAlarmListener listener, @Nullable Handler targetHandler) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, null,
listener, tag, targetHandler, null, null);
}
@@ -828,7 +879,7 @@ public class AlarmManager {
* @see Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM
*/
@RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
- public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
+ public void setAlarmClock(@NonNull AlarmClockInfo info, @NonNull PendingIntent operation) {
setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation,
null, null, (Handler) null, null, info);
}
@@ -837,7 +888,8 @@ public class AlarmManager {
@SystemApi
@RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
- long intervalMillis, PendingIntent operation, WorkSource workSource) {
+ long intervalMillis, @NonNull PendingIntent operation,
+ @Nullable WorkSource workSource) {
setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, null, null,
(Handler) null, workSource, null);
}
@@ -854,8 +906,8 @@ public class AlarmManager {
*/
@UnsupportedAppUsage
public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
- long intervalMillis, String tag, OnAlarmListener listener, Handler targetHandler,
- WorkSource workSource) {
+ long intervalMillis, @Nullable String tag, @NonNull OnAlarmListener listener,
+ @Nullable Handler targetHandler, @Nullable WorkSource workSource) {
setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, tag,
targetHandler, workSource, null);
}
@@ -873,8 +925,8 @@ public class AlarmManager {
@SystemApi
@RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
- long intervalMillis, OnAlarmListener listener, Handler targetHandler,
- WorkSource workSource) {
+ long intervalMillis, @NonNull OnAlarmListener listener, @Nullable Handler targetHandler,
+ @Nullable WorkSource workSource) {
setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, null,
targetHandler, workSource, null);
}
@@ -1072,7 +1124,7 @@ public class AlarmManager {
* @see Intent#EXTRA_ALARM_COUNT
*/
public void setInexactRepeating(@AlarmType int type, long triggerAtMillis,
- long intervalMillis, PendingIntent operation) {
+ long intervalMillis, @NonNull PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null,
null, (Handler) null, null, null);
}
@@ -1122,7 +1174,7 @@ public class AlarmManager {
* @see #RTC_WAKEUP
*/
public void setAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
- PendingIntent operation) {
+ @NonNull PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE,
operation, null, null, (Handler) null, null, null);
}
@@ -1195,12 +1247,46 @@ public class AlarmManager {
*/
@RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
- PendingIntent operation) {
+ @NonNull PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
null, null, (Handler) null, null, null);
}
/**
+ * Like {@link #setExact(int, long, String, Executor, WorkSource, OnAlarmListener)}, but this
+ * alarm will be allowed to execute even when the system is in low-power idle modes.
+ *
+ * <p> See {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} for more details.
+ *
+ * @param type type of alarm
+ * @param triggerAtMillis The exact time in milliseconds, that the alarm should be delivered,
+ * expressed in the appropriate clock's units (depending on the alarm
+ * type).
+ * @param listener {@link OnAlarmListener} instance whose
+ * {@link OnAlarmListener#onAlarm() onAlarm()} method will be called when
+ * the alarm time is reached.
+ * @param executor The {@link Executor} on which to execute the listener's onAlarm()
+ * callback.
+ * @param tag Optional. A string tag used to identify this alarm in logs and
+ * battery-attribution.
+ * @param workSource A {@link WorkSource} object to attribute this alarm to the app that
+ * requested this work.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ Manifest.permission.UPDATE_DEVICE_STATS,
+ Manifest.permission.SCHEDULE_EXACT_ALARM}, conditional = true)
+ public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
+ @Nullable String tag, @NonNull Executor executor, @Nullable WorkSource workSource,
+ @NonNull OnAlarmListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, null, listener, tag,
+ executor, workSource, null);
+ }
+
+ /**
* Remove any alarms with a matching {@link Intent}.
* Any alarm, of any type, whose Intent matches this one (as defined by
* {@link Intent#filterEquals}), will be canceled.
@@ -1210,7 +1296,7 @@ public class AlarmManager {
*
* @see #set
*/
- public void cancel(PendingIntent operation) {
+ public void cancel(@NonNull PendingIntent operation) {
if (operation == null) {
final String msg = "cancel() called with a null PendingIntent";
if (mTargetSdkVersion >= Build.VERSION_CODES.N) {
@@ -1233,7 +1319,7 @@ public class AlarmManager {
*
* @param listener OnAlarmListener instance that is the target of a currently-set alarm.
*/
- public void cancel(OnAlarmListener listener) {
+ public void cancel(@NonNull OnAlarmListener listener) {
if (listener == null) {
throw new NullPointerException("cancel() called with a null OnAlarmListener");
}
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
index 299ad66a882c..4a3a6d912966 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
+++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
@@ -113,7 +113,9 @@ public class EconomyManager {
/** @hide */
public static final String KEY_AM_INITIAL_CONSUMPTION_LIMIT = "am_initial_consumption_limit";
/** @hide */
- public static final String KEY_AM_HARD_CONSUMPTION_LIMIT = "am_hard_consumption_limit";
+ public static final String KEY_AM_MIN_CONSUMPTION_LIMIT = "am_minimum_consumption_limit";
+ /** @hide */
+ public static final String KEY_AM_MAX_CONSUMPTION_LIMIT = "am_maximum_consumption_limit";
// TODO: Add AlarmManager modifier keys
/** @hide */
public static final String KEY_AM_REWARD_TOP_ACTIVITY_INSTANT =
@@ -242,7 +244,9 @@ public class EconomyManager {
/** @hide */
public static final String KEY_JS_INITIAL_CONSUMPTION_LIMIT = "js_initial_consumption_limit";
/** @hide */
- public static final String KEY_JS_HARD_CONSUMPTION_LIMIT = "js_hard_consumption_limit";
+ public static final String KEY_JS_MIN_CONSUMPTION_LIMIT = "js_minimum_consumption_limit";
+ /** @hide */
+ public static final String KEY_JS_MAX_CONSUMPTION_LIMIT = "js_maximum_consumption_limit";
// TODO: Add JobScheduler modifier keys
/** @hide */
public static final String KEY_JS_REWARD_APP_INSTALL_INSTANT =
@@ -371,7 +375,9 @@ public class EconomyManager {
/** @hide */
public static final long DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES = arcToCake(2880);
/** @hide */
- public static final long DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES = arcToCake(15_000);
+ public static final long DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES = arcToCake(1440);
+ /** @hide */
+ public static final long DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES = arcToCake(15_000);
// TODO: add AlarmManager modifier default values
/** @hide */
public static final long DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT_CAKES = arcToCake(0);
@@ -478,8 +484,10 @@ public class EconomyManager {
/** @hide */
public static final long DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES = arcToCake(29_000);
/** @hide */
- // TODO: set hard limit based on device type (phone vs tablet vs etc) + battery size
- public static final long DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES = arcToCake(250_000);
+ public static final long DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES = arcToCake(17_000);
+ /** @hide */
+ // TODO: set maximum limit based on device type (phone vs tablet vs etc) + battery size
+ public static final long DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES = arcToCake(250_000);
// TODO: add JobScheduler modifier default values
/** @hide */
public static final long DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES = arcToCake(408);
diff --git a/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java b/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java
index a413f7b1f3ca..6b01a9f446af 100644
--- a/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java
+++ b/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java
@@ -22,6 +22,8 @@ import android.os.HandlerThread;
import android.os.Looper;
import android.os.Trace;
+import com.android.server.am.BroadcastLoopers;
+
import java.util.concurrent.Executor;
/**
@@ -45,6 +47,7 @@ public final class JobSchedulerBackgroundThread extends HandlerThread {
sInstance = new JobSchedulerBackgroundThread();
sInstance.start();
final Looper looper = sInstance.getLooper();
+ BroadcastLoopers.addLooper(looper);
looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER);
looper.setSlowLogThresholdMs(
SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 6375d0deae40..f659dbff72c7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -432,6 +432,7 @@ public class JobSchedulerService extends com.android.server.SystemService
break;
case Constants.KEY_MIN_LINEAR_BACKOFF_TIME_MS:
case Constants.KEY_MIN_EXP_BACKOFF_TIME_MS:
+ case Constants.KEY_SYSTEM_STOP_TO_FAILURE_RATIO:
mConstants.updateBackoffConstantsLocked();
break;
case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
@@ -509,6 +510,8 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final String KEY_MIN_LINEAR_BACKOFF_TIME_MS = "min_linear_backoff_time_ms";
private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms";
+ private static final String KEY_SYSTEM_STOP_TO_FAILURE_RATIO =
+ "system_stop_to_failure_ratio";
private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
private static final String KEY_CONN_USE_CELL_SIGNAL_STRENGTH =
@@ -540,6 +543,7 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
+ private static final int DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO = 3;
private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true;
@@ -589,6 +593,11 @@ public class JobSchedulerService extends com.android.server.SystemService
* The minimum backoff time to allow for exponential backoff.
*/
long MIN_EXP_BACKOFF_TIME_MS = DEFAULT_MIN_EXP_BACKOFF_TIME_MS;
+ /**
+ * The ratio to use to convert number of times a job was stopped by JobScheduler to an
+ * incremental failure in the backoff policy calculation.
+ */
+ int SYSTEM_STOP_TO_FAILURE_RATIO = DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO;
/**
* The fraction of a job's running window that must pass before we
@@ -700,6 +709,9 @@ public class JobSchedulerService extends com.android.server.SystemService
MIN_EXP_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_MIN_EXP_BACKOFF_TIME_MS,
DEFAULT_MIN_EXP_BACKOFF_TIME_MS);
+ SYSTEM_STOP_TO_FAILURE_RATIO = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_SYSTEM_STOP_TO_FAILURE_RATIO,
+ DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO);
}
private void updateConnectivityConstantsLocked() {
@@ -797,6 +809,7 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println();
pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
+ pw.print(KEY_SYSTEM_STOP_TO_FAILURE_RATIO, SYSTEM_STOP_TO_FAILURE_RATIO).println();
pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
pw.print(KEY_CONN_USE_CELL_SIGNAL_STRENGTH, CONN_USE_CELL_SIGNAL_STRENGTH).println();
@@ -1277,7 +1290,7 @@ public class JobSchedulerService extends com.android.server.SystemService
jobStatus.getJob().isPrefetch(),
jobStatus.getJob().getPriority(),
jobStatus.getEffectivePriority(),
- jobStatus.getNumFailures());
+ jobStatus.getNumPreviousAttempts());
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -1476,7 +1489,7 @@ public class JobSchedulerService extends com.android.server.SystemService
cancelled.getJob().isPrefetch(),
cancelled.getJob().getPriority(),
cancelled.getEffectivePriority(),
- cancelled.getNumFailures());
+ cancelled.getNumPreviousAttempts());
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
@@ -1903,7 +1916,7 @@ public class JobSchedulerService extends com.android.server.SystemService
* Reschedules the given job based on the job's backoff policy. It doesn't make sense to
* specify an override deadline on a failed job (the failed job will run even though it's not
* ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
- * ready job with {@link JobStatus#getNumFailures()} > 0 will be executed.
+ * ready job with {@link JobStatus#getNumPreviousAttempts()} > 0 will be executed.
*
* @param failureToReschedule Provided job status that we will reschedule.
* @return A newly instantiated JobStatus with the same constraints as the last job except
@@ -1911,12 +1924,24 @@ public class JobSchedulerService extends com.android.server.SystemService
* @see #maybeQueueReadyJobsForExecutionLocked
*/
@VisibleForTesting
- JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
+ JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule,
+ int internalStopReason) {
final long elapsedNowMillis = sElapsedRealtimeClock.millis();
final JobInfo job = failureToReschedule.getJob();
final long initialBackoffMillis = job.getInitialBackoffMillis();
- final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
+ int numFailures = failureToReschedule.getNumFailures();
+ int numSystemStops = failureToReschedule.getNumSystemStops();
+ // We should back off slowly if JobScheduler keeps stopping the job,
+ // but back off immediately if the issue appeared to be the app's fault.
+ if (internalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH
+ || internalStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT) {
+ numFailures++;
+ } else {
+ numSystemStops++;
+ }
+ final int backoffAttempts = Math.max(1,
+ numFailures + numSystemStops / mConstants.SYSTEM_STOP_TO_FAILURE_RATIO);
long delayMillis;
switch (job.getBackoffPolicy()) {
@@ -1943,7 +1968,7 @@ public class JobSchedulerService extends com.android.server.SystemService
Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
JobStatus newJob = new JobStatus(failureToReschedule,
elapsedNowMillis + delayMillis,
- JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
+ JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops,
failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
if (job.isPeriodic()) {
newJob.setOriginalLatestRunTimeElapsed(
@@ -2034,7 +2059,7 @@ public class JobSchedulerService extends com.android.server.SystemService
+ newLatestRuntimeElapsed);
return new JobStatus(periodicToReschedule,
elapsedNow + period - flex, elapsedNow + period,
- 0 /* backoffAttempt */,
+ 0 /* numFailures */, 0 /* numSystemStops */,
sSystemClock.millis() /* lastSuccessfulRunTime */,
periodicToReschedule.getLastFailedRunTime());
}
@@ -2049,7 +2074,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
return new JobStatus(periodicToReschedule,
newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
- 0 /* backoffAttempt */,
+ 0 /* numFailures */, 0 /* numSystemStops */,
sSystemClock.millis() /* lastSuccessfulRunTime */,
periodicToReschedule.getLastFailedRunTime());
}
@@ -2093,7 +2118,7 @@ public class JobSchedulerService extends com.android.server.SystemService
// job so we can transfer any appropriate state over from the previous job when
// we stop it.
final JobStatus rescheduledJob = needsReschedule
- ? getRescheduleJobForFailureLocked(jobStatus) : null;
+ ? getRescheduleJobForFailureLocked(jobStatus, debugStopReason) : null;
if (rescheduledJob != null
&& (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT
|| debugStopReason == JobParameters.INTERNAL_STOP_REASON_PREEMPT)) {
@@ -2427,7 +2452,7 @@ public class JobSchedulerService extends com.android.server.SystemService
shouldForceBatchJob =
mPrefetchController.getNextEstimatedLaunchTimeLocked(job)
> relativelySoonCutoffTime;
- } else if (job.getNumFailures() > 0) {
+ } else if (job.getNumPreviousAttempts() > 0) {
shouldForceBatchJob = false;
} else {
final long nowElapsed = sElapsedRealtimeClock.millis();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index d6456f0edea5..9e3f19de56f1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -363,7 +363,7 @@ public final class JobServiceContext implements ServiceConnection {
job.getJob().isPrefetch(),
job.getJob().getPriority(),
job.getEffectivePriority(),
- job.getNumFailures());
+ job.getNumPreviousAttempts());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
// Use the context's ID to distinguish traces since there'll only be one job
// running per context.
@@ -1032,7 +1032,7 @@ public final class JobServiceContext implements ServiceConnection {
completedJob.getJob().isPrefetch(),
completedJob.getJob().getPriority(),
completedJob.getEffectivePriority(),
- completedJob.getNumFailures());
+ completedJob.getNumPreviousAttempts());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
completedJob.getTag(), getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 22b09683187f..df8f729b7ec8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -194,7 +194,7 @@ public final class JobStore {
convertRtcBoundsToElapsed(utcTimes, elapsedNow);
JobStatus newJob = new JobStatus(job,
elapsedRuntimes.first, elapsedRuntimes.second,
- 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime());
+ 0, 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime());
newJob.prepareLocked();
toAdd.add(newJob);
toRemove.add(job);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 547f94badd69..0eb93367e5e2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -342,10 +342,10 @@ public final class FlexibilityController extends StateController {
// There is no deadline and no estimated launch time.
return NO_LIFECYCLE_END;
}
- if (js.getNumFailures() > 1) {
- // Number of failures will not equal one as per restriction in JobStatus constructor.
+ // Increase the flex deadline for jobs rescheduled more than once.
+ if (js.getNumPreviousAttempts() > 1) {
return earliest + Math.min(
- (long) Math.scalb(mRescheduledJobDeadline, js.getNumFailures() - 2),
+ (long) Math.scalb(mRescheduledJobDeadline, js.getNumPreviousAttempts() - 2),
mMaxRescheduledDeadline);
}
return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index de602a8f7f25..f9fb0d0df002 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -241,10 +241,22 @@ public final class JobStatus {
*/
private long mOriginalLatestRunTimeElapsedMillis;
- /** How many times this job has failed, used to compute back-off. */
+ /**
+ * How many times this job has failed to complete on its own
+ * (via {@link android.app.job.JobService#jobFinished(JobParameters, boolean)} or because of
+ * a timeout).
+ * This count doesn't include most times JobScheduler decided to stop the job
+ * (via {@link android.app.job.JobService#onStopJob(JobParameters)}.
+ */
private final int numFailures;
/**
+ * The number of times JobScheduler has forced this job to stop due to reasons mostly outside
+ * of the app's control.
+ */
+ private final int mNumSystemStops;
+
+ /**
* Which app standby bucket this job's app is in. Updated when the app is moved to a
* different bucket.
*/
@@ -488,6 +500,8 @@ public final class JobStatus {
* @param tag A string associated with the job for debugging/logging purposes.
* @param numFailures Count of how many times this job has requested a reschedule because
* its work was not yet finished.
+ * @param numSystemStops Count of how many times JobScheduler has forced this job to stop due to
+ * factors mostly out of the app's control.
* @param earliestRunTimeElapsedMillis Milestone: earliest point in time at which the job
* is to be considered runnable
* @param latestRunTimeElapsedMillis Milestone: point in time at which the job will be
@@ -497,7 +511,7 @@ public final class JobStatus {
* @param internalFlags Non-API property flags about this job
*/
private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
- int sourceUserId, int standbyBucket, String tag, int numFailures,
+ int sourceUserId, int standbyBucket, String tag, int numFailures, int numSystemStops,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags,
int dynamicConstraints) {
@@ -535,6 +549,7 @@ public final class JobStatus {
this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
this.numFailures = numFailures;
+ mNumSystemStops = numSystemStops;
int requiredConstraints = job.getConstraintFlags();
if (job.getRequiredNetwork() != null) {
@@ -576,7 +591,7 @@ public final class JobStatus {
// Otherwise, every consecutive reschedule increases a jobs' flexibility deadline.
if (!isRequestedExpeditedJob()
&& satisfiesMinWindowException
- && numFailures != 1
+ && (numFailures + numSystemStops) != 1
&& lacksSomeFlexibleConstraints) {
mNumRequiredFlexibleConstraints =
NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0);
@@ -626,7 +641,7 @@ public final class JobStatus {
this(jobStatus.getJob(), jobStatus.getUid(),
jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
jobStatus.getStandbyBucket(),
- jobStatus.getSourceTag(), jobStatus.getNumFailures(),
+ jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getNumSystemStops(),
jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
jobStatus.getInternalFlags(), jobStatus.mDynamicConstraints);
@@ -654,7 +669,7 @@ public final class JobStatus {
int innerFlags, int dynamicConstraints) {
this(job, callingUid, sourcePkgName, sourceUserId,
standbyBucket,
- sourceTag, 0,
+ sourceTag, /* numFailures */ 0, /* numSystemStops */ 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime, innerFlags, dynamicConstraints);
@@ -673,12 +688,13 @@ public final class JobStatus {
/** Create a new job to be rescheduled with the provided parameters. */
public JobStatus(JobStatus rescheduling,
long newEarliestRuntimeElapsedMillis,
- long newLatestRuntimeElapsedMillis, int backoffAttempt,
+ long newLatestRuntimeElapsedMillis, int numFailures, int numSystemStops,
long lastSuccessfulRunTime, long lastFailedRunTime) {
this(rescheduling.job, rescheduling.getUid(),
rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
rescheduling.getStandbyBucket(),
- rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
+ rescheduling.getSourceTag(), numFailures, numSystemStops,
+ newEarliestRuntimeElapsedMillis,
newLatestRuntimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags(),
rescheduling.mDynamicConstraints);
@@ -715,7 +731,7 @@ public final class JobStatus {
int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
sourceUserId, elapsedNow);
return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
- standbyBucket, tag, 0,
+ standbyBucket, tag, /* numFailures */ 0, /* numSystemStops */ 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
/*innerFlags=*/ 0, /* dynamicConstraints */ 0);
@@ -868,10 +884,27 @@ public final class JobStatus {
pw.print(job.getId());
}
+ /**
+ * Returns the number of times the job stopped previously for reasons that appeared to be within
+ * the app's control.
+ */
public int getNumFailures() {
return numFailures;
}
+ /**
+ * Returns the number of times the system stopped a previous execution of this job for reasons
+ * that were likely outside the app's control.
+ */
+ public int getNumSystemStops() {
+ return mNumSystemStops;
+ }
+
+ /** Returns the total number of times we've attempted to run this job in the past. */
+ public int getNumPreviousAttempts() {
+ return numFailures + mNumSystemStops;
+ }
+
public ComponentName getServiceComponent() {
return job.getService();
}
@@ -1857,6 +1890,10 @@ public final class JobStatus {
sb.append(" failures=");
sb.append(numFailures);
}
+ if (mNumSystemStops != 0) {
+ sb.append(" system stops=");
+ sb.append(mNumSystemStops);
+ }
if (isReady()) {
sb.append(" READY");
} else {
@@ -2382,6 +2419,9 @@ public final class JobStatus {
if (numFailures != 0) {
pw.print("Num failures: "); pw.println(numFailures);
}
+ if (mNumSystemStops != 0) {
+ pw.print("Num system stops: "); pw.println(mNumSystemStops);
+ }
if (mLastSuccessfulRunTime != 0) {
pw.print("Last successful run: ");
pw.println(formatTime(mLastSuccessfulRunTime));
@@ -2579,7 +2619,7 @@ public final class JobStatus {
proto.write(JobStatusDumpProto.ORIGINAL_LATEST_RUNTIME_ELAPSED,
mOriginalLatestRunTimeElapsedMillis);
- proto.write(JobStatusDumpProto.NUM_FAILURES, numFailures);
+ proto.write(JobStatusDumpProto.NUM_FAILURES, numFailures + mNumSystemStops);
proto.write(JobStatusDumpProto.LAST_SUCCESSFUL_RUN_TIME, mLastSuccessfulRunTime);
proto.write(JobStatusDumpProto.LAST_FAILED_RUN_TIME, mLastFailedRunTime);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index b426f16744e3..46338fa69eb0 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -33,9 +33,10 @@ import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NO
import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES;
-import static android.app.tare.EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES;
+import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES;
+import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES;
@@ -71,9 +72,10 @@ import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_NONWAK
import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP;
import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE;
import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP;
-import static android.app.tare.EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT;
+import static android.app.tare.EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_AM_MAX_SATIATED_BALANCE;
+import static android.app.tare.EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED;
import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP;
import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT;
@@ -146,7 +148,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
private long mMinSatiatedBalanceOther;
private long mMaxSatiatedBalance;
private long mInitialSatiatedConsumptionLimit;
- private long mHardSatiatedConsumptionLimit;
+ private long mMinSatiatedConsumptionLimit;
+ private long mMaxSatiatedConsumptionLimit;
private final KeyValueListParser mParser = new KeyValueListParser(',');
private final Injector mInjector;
@@ -199,8 +202,13 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
}
@Override
- long getHardSatiatedConsumptionLimit() {
- return mHardSatiatedConsumptionLimit;
+ long getMinSatiatedConsumptionLimit() {
+ return mMinSatiatedConsumptionLimit;
+ }
+
+ @Override
+ long getMaxSatiatedConsumptionLimit() {
+ return mMaxSatiatedConsumptionLimit;
}
@NonNull
@@ -240,12 +248,15 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
mMaxSatiatedBalance = getConstantAsCake(mParser, properties,
KEY_AM_MAX_SATIATED_BALANCE, DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
Math.max(arcToCake(1), mMinSatiatedBalanceExempted));
+ mMinSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+ KEY_AM_MIN_CONSUMPTION_LIMIT, DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES,
+ arcToCake(1));
mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
- KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
- arcToCake(1));
- mHardSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
- KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
- mInitialSatiatedConsumptionLimit);
+ KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
+ mMinSatiatedConsumptionLimit);
+ mMaxSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+ KEY_AM_MAX_CONSUMPTION_LIMIT, DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES,
+ mInitialSatiatedConsumptionLimit);
final long exactAllowWhileIdleWakeupBasePrice = getConstantAsCake(mParser, properties,
KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE,
@@ -396,9 +407,11 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
pw.decreaseIndent();
pw.print("Max satiated balance", cakeToString(mMaxSatiatedBalance)).println();
pw.print("Consumption limits: [");
+ pw.print(cakeToString(mMinSatiatedConsumptionLimit));
+ pw.print(", ");
pw.print(cakeToString(mInitialSatiatedConsumptionLimit));
pw.print(", ");
- pw.print(cakeToString(mHardSatiatedConsumptionLimit));
+ pw.print(cakeToString(mMaxSatiatedConsumptionLimit));
pw.println("]");
pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
index 66f7c357d223..7a9607657972 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
@@ -43,7 +43,8 @@ public class CompleteEconomicPolicy extends EconomicPolicy {
private int mEnabledEconomicPolicyIds = 0;
private int[] mCostModifiers = EmptyArray.INT;
private long mInitialConsumptionLimit;
- private long mHardConsumptionLimit;
+ private long mMinConsumptionLimit;
+ private long mMaxConsumptionLimit;
CompleteEconomicPolicy(@NonNull InternalResourceService irs) {
this(irs, new CompleteInjector());
@@ -100,14 +101,17 @@ public class CompleteEconomicPolicy extends EconomicPolicy {
private void updateLimits() {
long initialConsumptionLimit = 0;
- long hardConsumptionLimit = 0;
+ long minConsumptionLimit = 0;
+ long maxConsumptionLimit = 0;
for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
final EconomicPolicy economicPolicy = mEnabledEconomicPolicies.valueAt(i);
initialConsumptionLimit += economicPolicy.getInitialSatiatedConsumptionLimit();
- hardConsumptionLimit += economicPolicy.getHardSatiatedConsumptionLimit();
+ minConsumptionLimit += economicPolicy.getMinSatiatedConsumptionLimit();
+ maxConsumptionLimit += economicPolicy.getMaxSatiatedConsumptionLimit();
}
mInitialConsumptionLimit = initialConsumptionLimit;
- mHardConsumptionLimit = hardConsumptionLimit;
+ mMinConsumptionLimit = minConsumptionLimit;
+ mMaxConsumptionLimit = maxConsumptionLimit;
}
@Override
@@ -134,8 +138,13 @@ public class CompleteEconomicPolicy extends EconomicPolicy {
}
@Override
- long getHardSatiatedConsumptionLimit() {
- return mHardConsumptionLimit;
+ long getMinSatiatedConsumptionLimit() {
+ return mMinConsumptionLimit;
+ }
+
+ @Override
+ long getMaxSatiatedConsumptionLimit() {
+ return mMaxConsumptionLimit;
}
@NonNull
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index 008dcb8edf63..b52f6f11b3bf 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -232,15 +232,21 @@ public abstract class EconomicPolicy {
* Returns the maximum number of cakes that should be consumed during a full 100% discharge
* cycle. This is the initial limit. The system may choose to increase the limit over time,
* but the increased limit should never exceed the value returned from
- * {@link #getHardSatiatedConsumptionLimit()}.
+ * {@link #getMaxSatiatedConsumptionLimit()}.
*/
abstract long getInitialSatiatedConsumptionLimit();
/**
- * Returns the maximum number of cakes that should be consumed during a full 100% discharge
- * cycle. This is the hard limit that should never be exceeded.
+ * Returns the minimum number of cakes that should be available for consumption during a full
+ * 100% discharge cycle.
+ */
+ abstract long getMinSatiatedConsumptionLimit();
+
+ /**
+ * Returns the maximum number of cakes that should be available for consumption during a full
+ * 100% discharge cycle.
*/
- abstract long getHardSatiatedConsumptionLimit();
+ abstract long getMaxSatiatedConsumptionLimit();
/** Return the set of modifiers that should apply to this policy's costs. */
@NonNull
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index dd0a19433683..581a545f8401 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -670,7 +670,7 @@ public class InternalResourceService extends SystemService {
final long shortfall = (mCurrentBatteryLevel - QUANTITATIVE_EASING_BATTERY_THRESHOLD)
* currentConsumptionLimit / 100;
final long newConsumptionLimit = Math.min(currentConsumptionLimit + shortfall,
- mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit());
+ mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit());
if (newConsumptionLimit != currentConsumptionLimit) {
Slog.i(TAG, "Increasing consumption limit from " + cakeToString(currentConsumptionLimit)
+ " to " + cakeToString(newConsumptionLimit));
@@ -720,12 +720,12 @@ public class InternalResourceService extends SystemService {
// The stock is too low. We're doing pretty well. We can increase the stock slightly
// to let apps do more work in the background.
newConsumptionLimit = Math.min((long) (currentConsumptionLimit * 1.01),
- mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit());
+ mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit());
} else if (percentageOfTarget < 100) {
// The stock is too high IMO. We're below the target. Decrease the stock to reduce
// background work.
newConsumptionLimit = Math.max((long) (currentConsumptionLimit * .98),
- mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ mCompleteEconomicPolicy.getMinSatiatedConsumptionLimit());
} else {
// The stock is just right.
return;
@@ -957,9 +957,9 @@ public class InternalResourceService extends SystemService {
} else {
mScribe.loadFromDiskLocked();
if (mScribe.getSatiatedConsumptionLimitLocked()
- < mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()
+ < mCompleteEconomicPolicy.getMinSatiatedConsumptionLimit()
|| mScribe.getSatiatedConsumptionLimitLocked()
- > mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) {
+ > mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit()) {
// Reset the consumption limit since several factors may have changed.
mScribe.setConsumptionLimitLocked(
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
@@ -1442,17 +1442,16 @@ public class InternalResourceService extends SystemService {
private void updateEconomicPolicy() {
synchronized (mLock) {
- final long initialLimit =
- mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit();
- final long hardLimit = mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit();
+ final long minLimit = mCompleteEconomicPolicy.getMinSatiatedConsumptionLimit();
+ final long maxLimit = mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit();
final int oldEnabledPolicies = mCompleteEconomicPolicy.getEnabledPolicyIds();
mCompleteEconomicPolicy.tearDown();
mCompleteEconomicPolicy = new CompleteEconomicPolicy(InternalResourceService.this);
if (mIsEnabled && mBootPhase >= PHASE_THIRD_PARTY_APPS_CAN_START) {
mCompleteEconomicPolicy.setup(getAllDeviceConfigProperties());
- if (initialLimit != mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()
- || hardLimit
- != mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) {
+ if (minLimit != mCompleteEconomicPolicy.getMinSatiatedConsumptionLimit()
+ || maxLimit
+ != mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit()) {
// Reset the consumption limit since several factors may have changed.
mScribe.setConsumptionLimitLocked(
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index 71c6d099ac77..7cf459c2e494 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -38,9 +38,10 @@ import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_BA
import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_CTP_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP_CAKES;
-import static android.app.tare.EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES;
+import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES;
+import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES;
@@ -84,9 +85,10 @@ import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_START_BASE_P
import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_START_CTP;
import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE;
import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP;
-import static android.app.tare.EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT;
+import static android.app.tare.EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_JS_MAX_SATIATED_BALANCE;
+import static android.app.tare.EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED;
import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER;
import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP;
@@ -159,7 +161,8 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
private long mMinSatiatedBalanceIncrementalAppUpdater;
private long mMaxSatiatedBalance;
private long mInitialSatiatedConsumptionLimit;
- private long mHardSatiatedConsumptionLimit;
+ private long mMinSatiatedConsumptionLimit;
+ private long mMaxSatiatedConsumptionLimit;
private final KeyValueListParser mParser = new KeyValueListParser(',');
private final Injector mInjector;
@@ -216,8 +219,13 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
}
@Override
- long getHardSatiatedConsumptionLimit() {
- return mHardSatiatedConsumptionLimit;
+ long getMinSatiatedConsumptionLimit() {
+ return mMinSatiatedConsumptionLimit;
+ }
+
+ @Override
+ long getMaxSatiatedConsumptionLimit() {
+ return mMaxSatiatedConsumptionLimit;
}
@NonNull
@@ -260,12 +268,15 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
mMaxSatiatedBalance = getConstantAsCake(mParser, properties,
KEY_JS_MAX_SATIATED_BALANCE, DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
Math.max(arcToCake(1), mMinSatiatedBalanceExempted));
+ mMinSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+ KEY_JS_MIN_CONSUMPTION_LIMIT, DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES,
+ arcToCake(1));
mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
- KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES,
- arcToCake(1));
- mHardSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
- KEY_JS_HARD_CONSUMPTION_LIMIT, DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES,
- mInitialSatiatedConsumptionLimit);
+ KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES,
+ mMinSatiatedConsumptionLimit);
+ mMaxSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+ KEY_JS_MAX_CONSUMPTION_LIMIT, DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES,
+ mInitialSatiatedConsumptionLimit);
mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START,
getConstantAsCake(mParser, properties,
@@ -420,9 +431,11 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
pw.decreaseIndent();
pw.print("Max satiated balance", cakeToString(mMaxSatiatedBalance)).println();
pw.print("Consumption limits: [");
+ pw.print(cakeToString(mMinSatiatedConsumptionLimit));
+ pw.print(", ");
pw.print(cakeToString(mInitialSatiatedConsumptionLimit));
pw.print(", ");
- pw.print(cakeToString(mHardSatiatedConsumptionLimit));
+ pw.print(cakeToString(mMaxSatiatedConsumptionLimit));
pw.println("]");
pw.println();
diff --git a/core/api/current.txt b/core/api/current.txt
index cfaacfe1fcc0..d1e6c8282abd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4604,23 +4604,24 @@ package android.app {
public class AlarmManager {
method public boolean canScheduleExactAlarms();
- method public void cancel(android.app.PendingIntent);
- method public void cancel(android.app.AlarmManager.OnAlarmListener);
+ method public void cancel(@NonNull android.app.PendingIntent);
+ method public void cancel(@NonNull android.app.AlarmManager.OnAlarmListener);
method public void cancelAll();
method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
- method public void set(int, long, android.app.PendingIntent);
- method public void set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
- method @RequiresPermission(android.Manifest.permission.SCHEDULE_EXACT_ALARM) public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
- method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent);
- method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExact(int, long, android.app.PendingIntent);
- method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
- method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
- method public void setInexactRepeating(int, long, long, android.app.PendingIntent);
- method public void setRepeating(int, long, long, android.app.PendingIntent);
+ method public void set(int, long, @NonNull android.app.PendingIntent);
+ method public void set(int, long, @Nullable String, @NonNull android.app.AlarmManager.OnAlarmListener, @Nullable android.os.Handler);
+ method @RequiresPermission(android.Manifest.permission.SCHEDULE_EXACT_ALARM) public void setAlarmClock(@NonNull android.app.AlarmManager.AlarmClockInfo, @NonNull android.app.PendingIntent);
+ method public void setAndAllowWhileIdle(int, long, @NonNull android.app.PendingIntent);
+ method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExact(int, long, @NonNull android.app.PendingIntent);
+ method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExact(int, long, @Nullable String, @NonNull android.app.AlarmManager.OnAlarmListener, @Nullable android.os.Handler);
+ method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExactAndAllowWhileIdle(int, long, @NonNull android.app.PendingIntent);
+ method public void setInexactRepeating(int, long, long, @NonNull android.app.PendingIntent);
+ method public void setRepeating(int, long, long, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.SET_TIME) public void setTime(long);
method @RequiresPermission(android.Manifest.permission.SET_TIME_ZONE) public void setTimeZone(String);
- method public void setWindow(int, long, long, android.app.PendingIntent);
- method public void setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
+ method public void setWindow(int, long, long, @NonNull android.app.PendingIntent);
+ method public void setWindow(int, long, long, @Nullable String, @NonNull android.app.AlarmManager.OnAlarmListener, @Nullable android.os.Handler);
+ method public void setWindow(int, long, long, @Nullable String, @NonNull java.util.concurrent.Executor, @NonNull android.app.AlarmManager.OnAlarmListener);
field public static final String ACTION_NEXT_ALARM_CLOCK_CHANGED = "android.app.action.NEXT_ALARM_CLOCK_CHANGED";
field public static final String ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED = "android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED";
field public static final int ELAPSED_REALTIME = 3; // 0x3
@@ -11654,7 +11655,7 @@ package android.content.pm {
public static final class PackageInstaller.PreapprovalDetails implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.graphics.Bitmap getIcon();
- method @NonNull public String getLabel();
+ method @NonNull public CharSequence getLabel();
method @NonNull public android.icu.util.ULocale getLocale();
method @NonNull public String getPackageName();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -11665,7 +11666,7 @@ package android.content.pm {
ctor public PackageInstaller.PreapprovalDetails.Builder();
method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails build();
method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setIcon(@NonNull android.graphics.Bitmap);
- method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLabel(@NonNull String);
+ method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLabel(@NonNull CharSequence);
method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLocale(@NonNull android.icu.util.ULocale);
method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setPackageName(@NonNull String);
}
@@ -41591,7 +41592,7 @@ package android.telephony {
field public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool";
field public static final String KEY_CARRIER_CONFIG_VERSION_STRING = "carrier_config_version_string";
field public static final String KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL = "carrier_cross_sim_ims_available_bool";
- field public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings";
+ field @Deprecated public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings";
field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY = "carrier_default_actions_on_dcfailure_string_array";
field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE = "carrier_default_actions_on_default_network_available_string_array";
field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY = "carrier_default_actions_on_redirection_string_array";
@@ -41728,6 +41729,7 @@ package android.telephony {
field public static final String KEY_MMS_MMS_READ_REPORT_ENABLED_BOOL = "enableMMSReadReports";
field public static final String KEY_MMS_MULTIPART_SMS_ENABLED_BOOL = "enableMultipartSMS";
field public static final String KEY_MMS_NAI_SUFFIX_STRING = "naiSuffix";
+ field public static final String KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT = "mms_network_release_timeout_millis_int";
field public static final String KEY_MMS_NOTIFY_WAP_MMSC_ENABLED_BOOL = "enabledNotifyWapMMSC";
field public static final String KEY_MMS_RECIPIENT_LIMIT_INT = "recipientLimit";
field public static final String KEY_MMS_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_BOOL = "sendMultipartSmsAsSeparateMessages";
@@ -43416,14 +43418,18 @@ package android.telephony {
field public static final int RESULT_RECEIVE_WHILE_ENCRYPTED = 504; // 0x1f8
field public static final int RESULT_REMOTE_EXCEPTION = 31; // 0x1f
field public static final int RESULT_REQUEST_NOT_SUPPORTED = 24; // 0x18
+ field public static final int RESULT_RIL_ABORTED = 137; // 0x89
field public static final int RESULT_RIL_ACCESS_BARRED = 122; // 0x7a
field public static final int RESULT_RIL_BLOCKED_DUE_TO_CALL = 123; // 0x7b
field public static final int RESULT_RIL_CANCELLED = 119; // 0x77
+ field public static final int RESULT_RIL_DEVICE_IN_USE = 136; // 0x88
field public static final int RESULT_RIL_ENCODING_ERR = 109; // 0x6d
field public static final int RESULT_RIL_GENERIC_ERROR = 124; // 0x7c
field public static final int RESULT_RIL_INTERNAL_ERR = 113; // 0x71
field public static final int RESULT_RIL_INVALID_ARGUMENTS = 104; // 0x68
field public static final int RESULT_RIL_INVALID_MODEM_STATE = 115; // 0x73
+ field public static final int RESULT_RIL_INVALID_RESPONSE = 125; // 0x7d
+ field public static final int RESULT_RIL_INVALID_SIM_STATE = 130; // 0x82
field public static final int RESULT_RIL_INVALID_SMSC_ADDRESS = 110; // 0x6e
field public static final int RESULT_RIL_INVALID_SMS_FORMAT = 107; // 0x6b
field public static final int RESULT_RIL_INVALID_STATE = 103; // 0x67
@@ -43432,14 +43438,23 @@ package android.telephony {
field public static final int RESULT_RIL_NETWORK_NOT_READY = 116; // 0x74
field public static final int RESULT_RIL_NETWORK_REJECT = 102; // 0x66
field public static final int RESULT_RIL_NO_MEMORY = 105; // 0x69
+ field public static final int RESULT_RIL_NO_NETWORK_FOUND = 135; // 0x87
field public static final int RESULT_RIL_NO_RESOURCES = 118; // 0x76
+ field public static final int RESULT_RIL_NO_SMS_TO_ACK = 131; // 0x83
+ field public static final int RESULT_RIL_NO_SUBSCRIPTION = 134; // 0x86
field public static final int RESULT_RIL_OPERATION_NOT_ALLOWED = 117; // 0x75
field public static final int RESULT_RIL_RADIO_NOT_AVAILABLE = 100; // 0x64
field public static final int RESULT_RIL_REQUEST_NOT_SUPPORTED = 114; // 0x72
field public static final int RESULT_RIL_REQUEST_RATE_LIMITED = 106; // 0x6a
field public static final int RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED = 121; // 0x79
field public static final int RESULT_RIL_SIM_ABSENT = 120; // 0x78
+ field public static final int RESULT_RIL_SIM_BUSY = 132; // 0x84
+ field public static final int RESULT_RIL_SIM_ERROR = 129; // 0x81
+ field public static final int RESULT_RIL_SIM_FULL = 133; // 0x85
+ field public static final int RESULT_RIL_SIM_PIN2 = 126; // 0x7e
+ field public static final int RESULT_RIL_SIM_PUK2 = 127; // 0x7f
field public static final int RESULT_RIL_SMS_SEND_FAIL_RETRY = 101; // 0x65
+ field public static final int RESULT_RIL_SUBSCRIPTION_NOT_AVAILABLE = 128; // 0x80
field public static final int RESULT_RIL_SYSTEM_ERR = 108; // 0x6c
field public static final int RESULT_SMS_BLOCKED_DURING_EMERGENCY = 29; // 0x1d
field public static final int RESULT_SMS_SEND_RETRY_FAILED = 30; // 0x1e
@@ -45568,6 +45583,14 @@ package android.text {
field public static final int DONE = -1; // 0xffffffff
}
+ public static class SegmentFinder.DefaultSegmentFinder extends android.text.SegmentFinder {
+ ctor public SegmentFinder.DefaultSegmentFinder(@NonNull int[]);
+ method public int nextEndBoundary(@IntRange(from=0) int);
+ method public int nextStartBoundary(@IntRange(from=0) int);
+ method public int previousEndBoundary(@IntRange(from=0) int);
+ method public int previousStartBoundary(@IntRange(from=0) int);
+ }
+
public class Selection {
method public static boolean extendDown(android.text.Spannable, android.text.Layout);
method public static boolean extendLeft(android.text.Spannable, android.text.Layout);
@@ -48557,6 +48580,12 @@ package android.view {
field public static final int VERTICAL_GRAVITY_MASK = 112; // 0x70
}
+ public class HandwritingDelegateConfiguration {
+ ctor public HandwritingDelegateConfiguration(@IdRes int, @NonNull Runnable);
+ method public int getDelegatorViewId();
+ method @NonNull public Runnable getInitiationCallback();
+ }
+
public class HapticFeedbackConstants {
field public static final int CLOCK_TICK = 4; // 0x4
field public static final int CONFIRM = 16; // 0x10
@@ -49574,16 +49603,13 @@ package android.view {
}
public final class PixelCopy {
- method @NonNull public static android.view.PixelCopy.Request ofSurface(@NonNull android.view.Surface, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>);
- method @NonNull public static android.view.PixelCopy.Request ofSurface(@NonNull android.view.SurfaceView, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>);
- method @NonNull public static android.view.PixelCopy.Request ofWindow(@NonNull android.view.Window, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>);
- method @NonNull public static android.view.PixelCopy.Request ofWindow(@NonNull android.view.View, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>);
method public static void request(@NonNull android.view.SurfaceView, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
method public static void request(@NonNull android.view.SurfaceView, @Nullable android.graphics.Rect, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
method public static void request(@NonNull android.view.Surface, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
method public static void request(@NonNull android.view.Surface, @Nullable android.graphics.Rect, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
method public static void request(@NonNull android.view.Window, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
method public static void request(@NonNull android.view.Window, @Nullable android.graphics.Rect, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
+ method public static void request(@NonNull android.view.PixelCopy.Request);
field public static final int ERROR_DESTINATION_INVALID = 5; // 0x5
field public static final int ERROR_SOURCE_INVALID = 4; // 0x4
field public static final int ERROR_SOURCE_NO_DATA = 3; // 0x3
@@ -49592,19 +49618,28 @@ package android.view {
field public static final int SUCCESS = 0; // 0x0
}
- public static final class PixelCopy.CopyResult {
- method @NonNull public android.graphics.Bitmap getBitmap();
- method public int getStatus();
- }
-
public static interface PixelCopy.OnPixelCopyFinishedListener {
method public void onPixelCopyFinished(int);
}
public static final class PixelCopy.Request {
- method public void request();
- method @NonNull public android.view.PixelCopy.Request setDestinationBitmap(@Nullable android.graphics.Bitmap);
- method @NonNull public android.view.PixelCopy.Request setSourceRect(@Nullable android.graphics.Rect);
+ method @Nullable public android.graphics.Bitmap getDestinationBitmap();
+ method @Nullable public android.graphics.Rect getSourceRect();
+ method @NonNull public static android.view.PixelCopy.Request.Builder ofSurface(@NonNull android.view.Surface, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>);
+ method @NonNull public static android.view.PixelCopy.Request.Builder ofSurface(@NonNull android.view.SurfaceView, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>);
+ method @NonNull public static android.view.PixelCopy.Request.Builder ofWindow(@NonNull android.view.Window, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>);
+ method @NonNull public static android.view.PixelCopy.Request.Builder ofWindow(@NonNull android.view.View, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>);
+ }
+
+ public static final class PixelCopy.Request.Builder {
+ method @NonNull public android.view.PixelCopy.Request build();
+ method @NonNull public android.view.PixelCopy.Request.Builder setDestinationBitmap(@Nullable android.graphics.Bitmap);
+ method @NonNull public android.view.PixelCopy.Request.Builder setSourceRect(@Nullable android.graphics.Rect);
+ }
+
+ public static final class PixelCopy.Result {
+ method @NonNull public android.graphics.Bitmap getBitmap();
+ method public int getStatus();
}
public final class PointerIcon implements android.os.Parcelable {
@@ -50167,6 +50202,7 @@ package android.view {
method public float getHandwritingBoundsOffsetLeft();
method public float getHandwritingBoundsOffsetRight();
method public float getHandwritingBoundsOffsetTop();
+ method @Nullable public android.view.HandwritingDelegateConfiguration getHandwritingDelegateConfiguration();
method public final boolean getHasOverlappingRendering();
method public final int getHeight();
method public void getHitRect(android.graphics.Rect);
@@ -50533,6 +50569,7 @@ package android.view {
method public void setForegroundTintList(@Nullable android.content.res.ColorStateList);
method public void setForegroundTintMode(@Nullable android.graphics.PorterDuff.Mode);
method public void setHandwritingBoundsOffsets(float, float, float, float);
+ method public void setHandwritingDelegateConfiguration(@Nullable android.view.HandwritingDelegateConfiguration);
method public void setHapticFeedbackEnabled(boolean);
method public void setHasTransientState(boolean);
method public void setHorizontalFadingEdgeEnabled(boolean);
@@ -53575,6 +53612,7 @@ package android.view.inputmethod {
method public boolean reportFullscreenMode(boolean);
method public boolean requestCursorUpdates(int);
method public default boolean requestCursorUpdates(int, int);
+ method public default void requestTextBoundsInfo(@NonNull android.graphics.RectF, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.inputmethod.TextBoundsInfoResult>);
method public boolean sendKeyEvent(android.view.KeyEvent);
method public boolean setComposingRegion(int, int);
method public default boolean setComposingRegion(int, int, @Nullable android.view.inputmethod.TextAttribute);
@@ -53904,6 +53942,50 @@ package android.view.inputmethod {
method @NonNull public android.view.inputmethod.TextAttribute.Builder setTextConversionSuggestions(@NonNull java.util.List<java.lang.String>);
}
+ public final class TextBoundsInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @IntRange(from=0, to=125) public int getCharacterBidiLevel(int);
+ method @NonNull public android.graphics.RectF getCharacterBounds(int);
+ method public int getCharacterFlags(int);
+ method public int getEnd();
+ method @NonNull public android.text.SegmentFinder getGraphemeSegmentFinder();
+ method @NonNull public android.text.SegmentFinder getLineSegmentFinder();
+ method @NonNull public android.graphics.Matrix getMatrix();
+ method public int getStart();
+ method @NonNull public android.text.SegmentFinder getWordSegmentFinder();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextBoundsInfo> CREATOR;
+ field public static final int FLAG_CHARACTER_LINEFEED = 2; // 0x2
+ field public static final int FLAG_CHARACTER_PUNCTUATION = 4; // 0x4
+ field public static final int FLAG_CHARACTER_WHITESPACE = 1; // 0x1
+ field public static final int FLAG_LINE_IS_RTL = 8; // 0x8
+ }
+
+ public static final class TextBoundsInfo.Builder {
+ ctor public TextBoundsInfo.Builder();
+ method @NonNull public android.view.inputmethod.TextBoundsInfo build();
+ method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder clear();
+ method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setCharacterBidiLevel(@NonNull int[]);
+ method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setCharacterBounds(@NonNull float[]);
+ method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setCharacterFlags(@NonNull int[]);
+ method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setGraphemeSegmentFinder(@NonNull android.text.SegmentFinder);
+ method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setLineSegmentFinder(@NonNull android.text.SegmentFinder);
+ method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setMatrix(@NonNull android.graphics.Matrix);
+ method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setStartAndEnd(@IntRange(from=0) int, @IntRange(from=0) int);
+ method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setWordSegmentFinder(@NonNull android.text.SegmentFinder);
+ }
+
+ public final class TextBoundsInfoResult {
+ ctor public TextBoundsInfoResult(int);
+ ctor public TextBoundsInfoResult(int, @NonNull android.view.inputmethod.TextBoundsInfo);
+ method public int getResultCode();
+ method @Nullable public android.view.inputmethod.TextBoundsInfo getTextBoundsInfo();
+ field public static final int CODE_CANCELLED = 3; // 0x3
+ field public static final int CODE_FAILED = 2; // 0x2
+ field public static final int CODE_SUCCESS = 1; // 0x1
+ field public static final int CODE_UNSUPPORTED = 0; // 0x0
+ }
+
public final class TextSnapshot {
ctor public TextSnapshot(@NonNull android.view.inputmethod.SurroundingText, @IntRange(from=0xffffffff) int, @IntRange(from=0xffffffff) int, int);
method @IntRange(from=0xffffffff) public int getCompositionEnd();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 88efcced78fb..ce18745046ff 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -340,10 +340,6 @@ package android.os {
method public boolean shouldBypassCache(@NonNull Q);
}
- public interface Parcelable {
- method public default int getStability();
- }
-
public class Process {
method public static final int getAppUidForSdkSandboxUid(int);
method public static final boolean isSdkSandboxUid(int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a382ecfc99d3..7cbf4617d991 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -524,10 +524,12 @@ package android.app {
}
public class AlarmManager {
- method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource);
- method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.AlarmManager.OnAlarmListener, android.os.Handler, android.os.WorkSource);
+ method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, @NonNull android.app.PendingIntent, @Nullable android.os.WorkSource);
+ method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, @NonNull android.app.AlarmManager.OnAlarmListener, @Nullable android.os.Handler, @Nullable android.os.WorkSource);
method @RequiresPermission(allOf={android.Manifest.permission.UPDATE_DEVICE_STATS, android.Manifest.permission.SCHEDULE_EXACT_ALARM}, conditional=true) public void setExact(int, long, @Nullable String, @NonNull java.util.concurrent.Executor, @NonNull android.os.WorkSource, @NonNull android.app.AlarmManager.OnAlarmListener);
+ method @RequiresPermission(allOf={android.Manifest.permission.UPDATE_DEVICE_STATS, android.Manifest.permission.SCHEDULE_EXACT_ALARM}, conditional=true) public void setExactAndAllowWhileIdle(int, long, @Nullable String, @NonNull java.util.concurrent.Executor, @Nullable android.os.WorkSource, @NonNull android.app.AlarmManager.OnAlarmListener);
method @RequiresPermission(android.Manifest.permission.SCHEDULE_PRIORITIZED_ALARM) public void setPrioritized(int, long, long, @Nullable String, @NonNull java.util.concurrent.Executor, @NonNull android.app.AlarmManager.OnAlarmListener);
+ method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void setWindow(int, long, long, @Nullable String, @NonNull java.util.concurrent.Executor, @Nullable android.os.WorkSource, @NonNull android.app.AlarmManager.OnAlarmListener);
}
public class AppOpsManager {
@@ -9670,6 +9672,7 @@ package android.os {
}
public interface Parcelable {
+ method public default int getStability();
field public static final int PARCELABLE_STABILITY_LOCAL = 0; // 0x0
field public static final int PARCELABLE_STABILITY_VINTF = 1; // 0x1
}
@@ -9678,7 +9681,6 @@ package android.os {
ctor public ParcelableHolder(int);
method public int describeContents();
method @Nullable public <T extends android.os.Parcelable> T getParcelable(@NonNull Class<T>);
- method public int getStability();
method public void readFromParcel(@NonNull android.os.Parcel);
method public void setParcelable(@Nullable android.os.Parcelable);
method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ef74a3ef6c20..6e8713413d68 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -419,7 +419,7 @@ package android.app {
}
public final class SyncNotedAppOp implements android.os.Parcelable {
- ctor public SyncNotedAppOp(int, @IntRange(from=0L) int, @Nullable String, @NonNull String);
+ ctor public SyncNotedAppOp(int, @IntRange(from=0L) int, @Nullable String, @Nullable String);
}
public class TaskInfo {
diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java
index f156b30d5050..f674e882d34e 100644
--- a/core/java/android/app/SyncNotedAppOp.java
+++ b/core/java/android/app/SyncNotedAppOp.java
@@ -56,7 +56,7 @@ public final class SyncNotedAppOp implements Parcelable {
* The package this op applies to
* @hide
*/
- private final @NonNull String mPackageName;
+ private final @Nullable String mPackageName;
/**
* Native code relies on parcel ordering, do not change
@@ -64,7 +64,7 @@ public final class SyncNotedAppOp implements Parcelable {
*/
@TestApi
public SyncNotedAppOp(int opMode, @IntRange(from = 0L) int opCode,
- @Nullable String attributionTag, @NonNull String packageName) {
+ @Nullable String attributionTag, @Nullable String packageName) {
this.mOpCode = opCode;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mOpCode,
@@ -101,7 +101,7 @@ public final class SyncNotedAppOp implements Parcelable {
* @hide
*/
public SyncNotedAppOp(@IntRange(from = 0L) int opCode, @Nullable String attributionTag,
- @NonNull String packageName) {
+ @Nullable String packageName) {
this(AppOpsManager.MODE_IGNORED, opCode, attributionTag, packageName);
}
@@ -152,7 +152,7 @@ public final class SyncNotedAppOp implements Parcelable {
* @hide
*/
@DataClass.Generated.Member
- public @NonNull String getPackageName() {
+ public @Nullable String getPackageName() {
return mPackageName;
}
@@ -211,11 +211,12 @@ public final class SyncNotedAppOp implements Parcelable {
byte flg = 0;
if (mAttributionTag != null) flg |= 0x4;
+ if (mPackageName != null) flg |= 0x8;
dest.writeByte(flg);
dest.writeInt(mOpMode);
dest.writeInt(mOpCode);
if (mAttributionTag != null) dest.writeString(mAttributionTag);
- dest.writeString(mPackageName);
+ if (mPackageName != null) dest.writeString(mPackageName);
}
@Override
@@ -233,7 +234,7 @@ public final class SyncNotedAppOp implements Parcelable {
int opMode = in.readInt();
int opCode = in.readInt();
String attributionTag = (flg & 0x4) == 0 ? null : in.readString();
- String packageName = in.readString();
+ String packageName = (flg & 0x8) == 0 ? null : in.readString();
this.mOpMode = opMode;
this.mOpCode = opCode;
@@ -243,8 +244,6 @@ public final class SyncNotedAppOp implements Parcelable {
"to", AppOpsManager._NUM_OP - 1);
this.mAttributionTag = attributionTag;
this.mPackageName = packageName;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mPackageName);
// onConstructed(); // You can define this method to get a callback
}
@@ -264,10 +263,10 @@ public final class SyncNotedAppOp implements Parcelable {
};
@DataClass.Generated(
- time = 1643320427700L,
+ time = 1667247337573L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/app/SyncNotedAppOp.java",
- inputSignatures = "private final int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic int getOpMode()\nprivate java.lang.String opCodeToString()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false, genToString=true)")
+ inputSignatures = "private final int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.Nullable java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic int getOpMode()\nprivate java.lang.String opCodeToString()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 95e9c22373de..5e15b0a19a6a 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2456,7 +2456,12 @@ public class WallpaperManager {
public void waitForCompletion() {
try {
- mLatch.await(30, TimeUnit.SECONDS);
+ final boolean completed = mLatch.await(30, TimeUnit.SECONDS);
+ if (completed) {
+ Log.d(TAG, "Wallpaper set completion.");
+ } else {
+ Log.d(TAG, "Timeout waiting for wallpaper set completion!");
+ }
} catch (InterruptedException e) {
// This might be legit: the crop may take a very long time. Don't sweat
// it in that case; we are okay with display lagging behind in order to
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index d2fb1fb7fdf4..d7686e22756e 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -3346,7 +3346,7 @@ public class PackageInstaller {
/**
* The label representing the app to be installed.
*/
- private final @NonNull String mLabel;
+ private final @NonNull CharSequence mLabel;
/**
* The locale of the app label being used.
*/
@@ -3388,7 +3388,7 @@ public class PackageInstaller {
@DataClass.Generated.Member
public PreapprovalDetails(
@Nullable Bitmap icon,
- @NonNull String label,
+ @NonNull CharSequence label,
@NonNull ULocale locale,
@NonNull String packageName) {
this.mIcon = icon;
@@ -3417,7 +3417,7 @@ public class PackageInstaller {
* The label representing the app to be installed.
*/
@DataClass.Generated.Member
- public @NonNull String getLabel() {
+ public @NonNull CharSequence getLabel() {
return mLabel;
}
@@ -3461,7 +3461,7 @@ public class PackageInstaller {
if (mIcon != null) flg |= 0x1;
dest.writeByte(flg);
if (mIcon != null) mIcon.writeToParcel(dest, flags);
- dest.writeString8(mLabel);
+ dest.writeCharSequence(mLabel);
dest.writeString8(mLocale.toString());
dest.writeString8(mPackageName);
}
@@ -3479,7 +3479,7 @@ public class PackageInstaller {
byte flg = in.readByte();
Bitmap icon = (flg & 0x1) == 0 ? null : Bitmap.CREATOR.createFromParcel(in);
- String label = in.readString8();
+ CharSequence label = (CharSequence) in.readCharSequence();
ULocale locale = new ULocale(in.readString8());
String packageName = in.readString8();
@@ -3519,7 +3519,7 @@ public class PackageInstaller {
public static final class Builder {
private @Nullable Bitmap mIcon;
- private @NonNull String mLabel;
+ private @NonNull CharSequence mLabel;
private @NonNull ULocale mLocale;
private @NonNull String mPackageName;
@@ -3545,7 +3545,7 @@ public class PackageInstaller {
* The label representing the app to be installed.
*/
@DataClass.Generated.Member
- public @NonNull Builder setLabel(@NonNull String value) {
+ public @NonNull Builder setLabel(@NonNull CharSequence value) {
checkNotUsed();
mBuilderFieldsSet |= 0x2;
mLabel = value;
@@ -3596,10 +3596,10 @@ public class PackageInstaller {
}
@DataClass.Generated(
- time = 1664257135109L,
+ time = 1666748098353L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
- inputSignatures = "private final @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate final @android.annotation.NonNull java.lang.String mLabel\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nclass PreapprovalDetails extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genBuilder=true, genToString=true)")
+ inputSignatures = "private final @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate final @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nclass PreapprovalDetails extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genBuilder=true, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/credentials/Credential.java b/core/java/android/credentials/Credential.java
index fed259254239..db89170b0cd7 100644
--- a/core/java/android/credentials/Credential.java
+++ b/core/java/android/credentials/Credential.java
@@ -36,7 +36,8 @@ public final class Credential implements Parcelable {
*
* @hide
*/
- @NonNull public static final String TYPE_PASSWORD = "android.credentials.TYPE_PASSWORD";
+ @NonNull public static final String TYPE_PASSWORD_CREDENTIAL =
+ "android.credentials.TYPE_PASSWORD_CREDENTIAL";
/**
* The credential type.
diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
index 4f09beec81dd..95aecfe4c7af 100644
--- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
+++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
@@ -16,30 +16,29 @@
package android.inputmethodservice;
+import static android.view.inputmethod.TextBoundsInfoResult.CODE_CANCELLED;
+
import android.annotation.AnyThread;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.RectF;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
-import android.view.inputmethod.DeleteGesture;
-import android.view.inputmethod.DeleteRangeGesture;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.HandwritingGesture;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
-import android.view.inputmethod.InsertGesture;
-import android.view.inputmethod.JoinOrSplitGesture;
-import android.view.inputmethod.RemoveSpaceGesture;
-import android.view.inputmethod.SelectGesture;
-import android.view.inputmethod.SelectRangeGesture;
+import android.view.inputmethod.ParcelableHandwritingGesture;
import android.view.inputmethod.SurroundingText;
import android.view.inputmethod.TextAttribute;
+import android.view.inputmethod.TextBoundsInfo;
+import android.view.inputmethod.TextBoundsInfoResult;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.inputmethod.IRemoteInputConnection;
@@ -47,6 +46,7 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import java.util.function.IntConsumer;
/**
@@ -98,6 +98,44 @@ final class IRemoteInputConnectionInvoker {
};
/**
+ * Subclass of {@link ResultReceiver} used by
+ * {@link #requestTextBoundsInfo(RectF, Executor, Consumer)} for providing
+ * callback.
+ */
+ private static final class TextBoundsInfoResultReceiver extends ResultReceiver {
+ @Nullable
+ private Consumer<TextBoundsInfoResult> mConsumer;
+ @Nullable
+ private Executor mExecutor;
+
+ TextBoundsInfoResultReceiver(@NonNull Executor executor,
+ @NonNull Consumer<TextBoundsInfoResult> consumer) {
+ super(null);
+ mExecutor = executor;
+ mConsumer = consumer;
+ }
+
+ @Override
+ protected void onReceiveResult(@TextBoundsInfoResult.ResultCode int resultCode,
+ @Nullable Bundle resultData) {
+ synchronized (this) {
+ if (mExecutor != null && mConsumer != null) {
+ final TextBoundsInfoResult textBoundsInfoResult = new TextBoundsInfoResult(
+ resultCode, TextBoundsInfo.createFromBundle(resultData));
+ mExecutor.execute(() -> mConsumer.accept(textBoundsInfoResult));
+ // provide callback only once.
+ clear();
+ }
+ }
+ }
+
+ private void clear() {
+ mExecutor = null;
+ mConsumer = null;
+ }
+ }
+
+ /**
* Creates a new instance of {@link IRemoteInputConnectionInvoker} for the given
* {@link IRemoteInputConnection}.
*
@@ -637,50 +675,19 @@ final class IRemoteInputConnectionInvoker {
}
/**
- * Invokes one of {@link IRemoteInputConnection#performHandwritingSelectGesture},
- * {@link IRemoteInputConnection#performHandwritingSelectRangeGesture},
- * {@link IRemoteInputConnection#performHandwritingDeleteGesture},
- * {@link IRemoteInputConnection#performHandwritingDeleteRangeGesture},
- * {@link IRemoteInputConnection#performHandwritingInsertGesture},
- * {@link IRemoteInputConnection#performHandwritingRemoveSpaceGesture},
- * {@link IRemoteInputConnection#performHandwritingJoinOrSplitGesture}.
+ * Invokes {@link IRemoteInputConnection#performHandwritingGesture(
+ * InputConnectionCommandHeader, ParcelableHandwritingGesture, ResultReceiver)}.
*/
@AnyThread
- public void performHandwritingGesture(
- @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
- @Nullable IntConsumer consumer) {
-
+ public void performHandwritingGesture(@NonNull ParcelableHandwritingGesture gesture,
+ @Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer) {
ResultReceiver resultReceiver = null;
if (consumer != null) {
Objects.requireNonNull(executor);
resultReceiver = new IntResultReceiver(executor, consumer);
}
try {
- if (gesture instanceof SelectGesture) {
- mConnection.performHandwritingSelectGesture(
- createHeader(), (SelectGesture) gesture, resultReceiver);
- } else if (gesture instanceof SelectRangeGesture) {
- mConnection.performHandwritingSelectRangeGesture(
- createHeader(), (SelectRangeGesture) gesture, resultReceiver);
- } else if (gesture instanceof InsertGesture) {
- mConnection.performHandwritingInsertGesture(
- createHeader(), (InsertGesture) gesture, resultReceiver);
- } else if (gesture instanceof DeleteGesture) {
- mConnection.performHandwritingDeleteGesture(
- createHeader(), (DeleteGesture) gesture, resultReceiver);
- } else if (gesture instanceof DeleteRangeGesture) {
- mConnection.performHandwritingDeleteRangeGesture(
- createHeader(), (DeleteRangeGesture) gesture, resultReceiver);
- } else if (gesture instanceof RemoveSpaceGesture) {
- mConnection.performHandwritingRemoveSpaceGesture(
- createHeader(), (RemoveSpaceGesture) gesture, resultReceiver);
- } else if (gesture instanceof JoinOrSplitGesture) {
- mConnection.performHandwritingJoinOrSplitGesture(
- createHeader(), (JoinOrSplitGesture) gesture, resultReceiver);
- } else if (consumer != null && executor != null) {
- executor.execute(()
- -> consumer.accept(InputConnection.HANDWRITING_GESTURE_RESULT_UNSUPPORTED));
- }
+ mConnection.performHandwritingGesture(createHeader(), gesture, resultReceiver);
} catch (RemoteException e) {
if (consumer != null && executor != null) {
executor.execute(() -> consumer.accept(
@@ -736,6 +743,28 @@ final class IRemoteInputConnectionInvoker {
}
/**
+ * Invokes {@link IRemoteInputConnection#requestTextBoundsInfo(InputConnectionCommandHeader,
+ * RectF, ResultReceiver)}
+ * @param rectF {@code rectF} parameter to be passed.
+ * @param executor {@code Executor} parameter to be passed.
+ * @param consumer {@code Consumer} parameter to be passed.
+ */
+ @AnyThread
+ public void requestTextBoundsInfo(
+ @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<TextBoundsInfoResult> consumer) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(consumer);
+
+ final ResultReceiver resultReceiver = new TextBoundsInfoResultReceiver(executor, consumer);
+ try {
+ mConnection.requestTextBoundsInfo(createHeader(), rectF, resultReceiver);
+ } catch (RemoteException e) {
+ executor.execute(() -> consumer.accept(new TextBoundsInfoResult(CODE_CANCELLED)));
+ }
+ }
+
+ /**
* Invokes {@link IRemoteInputConnection#commitContent(InputConnectionCommandHeader,
* InputContentInfo, int, Bundle, AndroidFuture)}.
*
diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java
index 09e86c4c9650..976e71f2b85a 100644
--- a/core/java/android/inputmethodservice/RemoteInputConnection.java
+++ b/core/java/android/inputmethodservice/RemoteInputConnection.java
@@ -21,6 +21,7 @@ import android.annotation.CallbackExecutor;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.RectF;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
@@ -32,8 +33,10 @@ import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.HandwritingGesture;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.ParcelableHandwritingGesture;
import android.view.inputmethod.SurroundingText;
import android.view.inputmethod.TextAttribute;
+import android.view.inputmethod.TextBoundsInfoResult;
import com.android.internal.inputmethod.CancellationGroup;
import com.android.internal.inputmethod.CompletableFutureUtil;
@@ -44,6 +47,7 @@ import com.android.internal.inputmethod.InputConnectionProtoDumper;
import java.lang.ref.WeakReference;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import java.util.function.IntConsumer;
/**
@@ -418,7 +422,8 @@ final class RemoteInputConnection implements InputConnection {
public void performHandwritingGesture(
@NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
@Nullable IntConsumer consumer) {
- mInvoker.performHandwritingGesture(gesture, executor, consumer);
+ mInvoker.performHandwritingGesture(ParcelableHandwritingGesture.of(gesture), executor,
+ consumer);
}
@AnyThread
@@ -460,6 +465,13 @@ final class RemoteInputConnection implements InputConnection {
}
@AnyThread
+ public void requestTextBoundsInfo(
+ @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<TextBoundsInfoResult> consumer) {
+ mInvoker.requestTextBoundsInfo(rectF, executor, consumer);
+ }
+
+ @AnyThread
public Handler getHandler() {
// Nothing should happen when called from input method.
return null;
diff --git a/core/java/android/os/Parcelable.java b/core/java/android/os/Parcelable.java
index 8a8045714d46..a2b0486c1df5 100644
--- a/core/java/android/os/Parcelable.java
+++ b/core/java/android/os/Parcelable.java
@@ -188,7 +188,7 @@ public interface Parcelable {
* @return true if this parcelable is stable.
* @hide
*/
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
default @Stability int getStability() {
return PARCELABLE_STABILITY_LOCAL;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 897b7c3afe54..fab6f7b97790 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9876,6 +9876,13 @@ public final class Settings {
"fingerprint_side_fps_auth_downtime";
/**
+ * Whether or not a SFPS device is required to be interactive for auth to unlock the device.
+ * @hide
+ */
+ public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED =
+ "sfps_require_screen_on_to_auth_enabled";
+
+ /**
* Whether or not debugging is enabled.
* @hide
*/
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index a3fa979a6188..1d4ac25eb284 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -172,9 +172,11 @@ public final class CredentialEntry implements Parcelable {
* {@code credential}, or the {@code pendingIntent}.
*/
public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
- Preconditions.checkState(pendingIntent != null && mCredential != null,
- "credential is already set. Cannot set both the pendingIntent "
- + "and the credential");
+ if (pendingIntent != null) {
+ Preconditions.checkState(mCredential != null,
+ "credential is already set. Cannot set both the pendingIntent "
+ + "and the credential");
+ }
mPendingIntent = pendingIntent;
return this;
}
@@ -186,9 +188,11 @@ public final class CredentialEntry implements Parcelable {
* the {@code pendingIntent}, or the {@code credential}.
*/
public @NonNull Builder setCredential(@Nullable Credential credential) {
- Preconditions.checkState(credential != null && mPendingIntent != null,
- "pendingIntent is already set. Cannot set both the "
- + "pendingIntent and the credential");
+ if (credential != null) {
+ Preconditions.checkState(mPendingIntent != null,
+ "pendingIntent is already set. Cannot set both the "
+ + "pendingIntent and the credential");
+ }
mCredential = credential;
return this;
}
diff --git a/core/java/android/service/credentials/SaveEntry.java b/core/java/android/service/credentials/SaveEntry.java
index abe51d43bc48..55ff6ff9755f 100644
--- a/core/java/android/service/credentials/SaveEntry.java
+++ b/core/java/android/service/credentials/SaveEntry.java
@@ -17,17 +17,11 @@
package android.service.credentials;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.PendingIntent;
import android.app.slice.Slice;
-import android.credentials.Credential;
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.Preconditions;
-
-import java.util.Objects;
-
/**
* An entry to be shown on the UI. This entry represents where the credential to be created will
* be stored. Examples include user's account, family group etc.
@@ -36,13 +30,11 @@ import java.util.Objects;
*/
public final class SaveEntry implements Parcelable {
private final @NonNull Slice mSlice;
- private final @Nullable PendingIntent mPendingIntent;
- private final @Nullable Credential mCredential;
+ private final @NonNull PendingIntent mPendingIntent;
private SaveEntry(@NonNull Parcel in) {
mSlice = in.readTypedObject(Slice.CREATOR);
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
- mCredential = in.readTypedObject(Credential.CREATOR);
}
public static final @NonNull Creator<SaveEntry> CREATOR = new Creator<SaveEntry>() {
@@ -66,18 +58,23 @@ public final class SaveEntry implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeTypedObject(mSlice, flags);
dest.writeTypedObject(mPendingIntent, flags);
- dest.writeTypedObject(mCredential, flags);
}
- /* package-private */ SaveEntry(
+ /**
+ * Constructs a save entry to be displayed on the UI.
+ *
+ * @param slice the display content to be displayed on the UI, along with this entry
+ * @param pendingIntent the intent to be invoked when the user selects this entry
+ */
+ public SaveEntry(
@NonNull Slice slice,
- @Nullable PendingIntent pendingIntent,
- @Nullable Credential credential) {
+ @NonNull PendingIntent pendingIntent) {
this.mSlice = slice;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mSlice);
this.mPendingIntent = pendingIntent;
- this.mCredential = credential;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPendingIntent);
}
/** Returns the content to be displayed with this save entry on the UI. */
@@ -86,76 +83,7 @@ public final class SaveEntry implements Parcelable {
}
/** Returns the pendingIntent to be invoked when this save entry on the UI is selectcd. */
- public @Nullable PendingIntent getPendingIntent() {
+ public @NonNull PendingIntent getPendingIntent() {
return mPendingIntent;
}
-
- /** Returns the credential produced by the {@link CreateCredentialRequest}. */
- public @Nullable Credential getCredential() {
- return mCredential;
- }
-
- /**
- * A builder for {@link SaveEntry}.
- */
- public static final class Builder {
-
- private @NonNull Slice mSlice;
- private @Nullable PendingIntent mPendingIntent;
- private @Nullable Credential mCredential;
-
- /**
- * Builds the instance.
- * @param slice the content to be displayed with this save entry
- *
- * @throws NullPointerException If {@code slice} is null.
- */
- public Builder(@NonNull Slice slice) {
- mSlice = Objects.requireNonNull(slice, "slice must not be null");
- }
-
- /**
- * Sets the pendingIntent to be invoked when this entry is selected by the user.
- *
- * @throws IllegalStateException If {@code credential} is already set. Must only set either
- * {@code credential}, or the {@code pendingIntent}.
- */
- public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
- Preconditions.checkState(pendingIntent != null
- && mCredential != null, "credential is already set. Must only set "
- + "either the pendingIntent or the credential");
- mPendingIntent = pendingIntent;
- return this;
- }
-
- /**
- * Sets the credential to be returned when this entry is selected by the user.
- *
- * @throws IllegalStateException If {@code pendingIntent} is already set. Must only
- * set either the {@code pendingIntent}, or {@code credential}.
- */
- public @NonNull Builder setCredential(@Nullable Credential credential) {
- Preconditions.checkState(credential != null && mPendingIntent != null,
- "pendingIntent is already set. Must only set either the credential "
- + "or the pendingIntent");
- mCredential = credential;
- return this;
- }
-
- /**
- * Builds the instance.
- *
- * @throws IllegalStateException if both {@code pendingIntent} and {@code credential}
- * are null.
- */
- public @NonNull SaveEntry build() {
- Preconditions.checkState(mPendingIntent == null && mCredential == null,
- "pendingIntent and credential both must not be null. Must set "
- + "either the pendingIntnet or the credential");
- return new SaveEntry(
- mSlice,
- mPendingIntent,
- mCredential);
- }
- }
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index c6cd708c5967..37fc9f288c3e 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -575,6 +575,7 @@ public abstract class WallpaperService extends Service {
*/
public void reportEngineShown(boolean waitForEngineShown) {
if (mIWallpaperEngine.mShownReported) return;
+ Log.d(TAG, "reportEngineShown: shouldWait=" + waitForEngineShown);
if (!waitForEngineShown) {
Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN);
mCaller.removeMessages(MSG_REPORT_SHOWN);
diff --git a/core/java/android/text/SegmentFinder.java b/core/java/android/text/SegmentFinder.java
index c21c5774fa0a..be0094b28509 100644
--- a/core/java/android/text/SegmentFinder.java
+++ b/core/java/android/text/SegmentFinder.java
@@ -19,6 +19,13 @@ package android.text;
import android.annotation.IntRange;
import android.graphics.RectF;
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.Objects;
+
/**
* Finds text segment boundaries within text. Subclasses can implement different types of text
* segments. Grapheme clusters and words are examples of possible text segments. These are
@@ -63,4 +70,144 @@ public abstract class SegmentFinder {
* character offset, or {@code DONE} if there are none.
*/
public abstract int nextEndBoundary(@IntRange(from = 0) int offset);
+
+ /**
+ * The default {@link SegmentFinder} implementation based on given segment ranges.
+ */
+ public static class DefaultSegmentFinder extends SegmentFinder {
+ private final int[] mSegments;
+
+ /**
+ * Create a SegmentFinder with segments stored in an array, where i-th segment's start is
+ * stored at segments[2 * i] and end is stored at segments[2 * i + 1] respectively.
+ *
+ * <p> It is required that segments do not overlap, and are already sorted by their start
+ * indices. </p>
+ * @param segments the array that stores the segment ranges.
+ * @throws IllegalArgumentException if the given segments array's length is not even; the
+ * given segments are not sorted or there are segments overlap with others.
+ */
+ public DefaultSegmentFinder(@NonNull int[] segments) {
+ checkSegmentsValid(segments);
+ mSegments = segments;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int previousStartBoundary(@IntRange(from = 0) int offset) {
+ return findPrevious(offset, /* isStart = */ true);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int previousEndBoundary(@IntRange(from = 0) int offset) {
+ return findPrevious(offset, /* isStart = */ false);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int nextStartBoundary(@IntRange(from = 0) int offset) {
+ return findNext(offset, /* isStart = */ true);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int nextEndBoundary(@IntRange(from = 0) int offset) {
+ return findNext(offset, /* isStart = */ false);
+ }
+
+ private int findNext(int offset, boolean isStart) {
+ if (offset < 0) return DONE;
+ if (mSegments.length < 1 || offset > mSegments[mSegments.length - 1]) return DONE;
+
+ if (offset < mSegments[0]) {
+ return isStart ? mSegments[0] : mSegments[1];
+ }
+
+ int index = Arrays.binarySearch(mSegments, offset);
+ if (index >= 0) {
+ // mSegments may have duplicate elements (The previous segments end equals
+ // to the following segments start.) Move the index forwards since we are searching
+ // for the next segment.
+ if (index + 1 < mSegments.length && mSegments[index + 1] == offset) {
+ index = index + 1;
+ }
+ // Point the index to the first segment boundary larger than the given offset.
+ index += 1;
+ } else {
+ // binarySearch returns the insertion point, it's the first segment boundary larger
+ // than the given offset.
+ index = -(index + 1);
+ }
+ if (index >= mSegments.length) return DONE;
+
+ // +---------------------------------------+
+ // | | isStart | isEnd |
+ // |---------------+-----------+-----------|
+ // | indexIsStart | index | index + 1 |
+ // |---------------+-----------+-----------|
+ // | indexIsEnd | index + 1 | index |
+ // +---------------------------------------+
+ boolean indexIsStart = index % 2 == 0;
+ if (isStart != indexIsStart) {
+ return (index + 1 < mSegments.length) ? mSegments[index + 1] : DONE;
+ }
+ return mSegments[index];
+ }
+
+ private int findPrevious(int offset, boolean isStart) {
+ if (mSegments.length < 1 || offset < mSegments[0]) return DONE;
+
+ if (offset > mSegments[mSegments.length - 1]) {
+ return isStart ? mSegments[mSegments.length - 2] : mSegments[mSegments.length - 1];
+ }
+
+ int index = Arrays.binarySearch(mSegments, offset);
+ if (index >= 0) {
+ // mSegments may have duplicate elements (when the previous segments end equal
+ // to the following segments start). Move the index backwards since we are searching
+ // for the previous segment.
+ if (index > 0 && mSegments[index - 1] == offset) {
+ index = index - 1;
+ }
+ // Point the index to the first segment boundary smaller than the given offset.
+ index -= 1;
+ } else {
+ // binarySearch returns the insertion point, insertionPoint - 1 is the first
+ // segment boundary smaller than the given offset.
+ index = -(index + 1) - 1;
+ }
+ if (index < 0) return DONE;
+
+ // +---------------------------------------+
+ // | | isStart | isEnd |
+ // |---------------+-----------+-----------|
+ // | indexIsStart | index | index - 1 |
+ // |---------------+-----------+-----------|
+ // | indexIsEnd | index - 1 | index |
+ // +---------------------------------------+
+ boolean indexIsStart = index % 2 == 0;
+ if (isStart != indexIsStart) {
+ return (index > 0) ? mSegments[index - 1] : DONE;
+ }
+ return mSegments[index];
+ }
+
+ private static void checkSegmentsValid(int[] segments) {
+ Objects.requireNonNull(segments);
+ Preconditions.checkArgument(segments.length % 2 == 0,
+ "the length of segments must be even");
+ if (segments.length == 0) return;
+ int lastSegmentEnd = Integer.MIN_VALUE;
+ for (int index = 0; index < segments.length; index += 2) {
+ if (segments[index] < lastSegmentEnd) {
+ throw new IllegalArgumentException("segments can't overlap");
+ }
+ if (segments[index] >= segments[index + 1]) {
+ throw new IllegalArgumentException("the segment range can't be empty");
+ }
+ lastSegmentEnd = segments[index + 1];
+ }
+ }
+ }
}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index d1f05ec0c05e..7b6a6d29baf8 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -103,6 +103,25 @@ public class FeatureFlagUtils {
public static final String SETTINGS_NEW_KEYBOARD_UI = "settings_new_keyboard_ui";
/**
+ * Enable new shortcut list UI
+ * @hide
+ */
+ public static final String SETTINGS_NEW_KEYBOARD_SHORTCUT = "settings_new_keyboard_shortcut";
+
+ /**
+ * Enable new modifier key settings UI
+ * @hide
+ */
+ public static final String SETTINGS_NEW_KEYBOARD_MODIFIER_KEY =
+ "settings_new_keyboard_modifier_key";
+
+ /**
+ * Enable new trackpad settings UI
+ * @hide
+ */
+ public static final String SETTINGS_NEW_KEYBOARD_TRACKPAD = "settings_new_keyboard_trackpad";
+
+ /**
* Enable the new pages which is implemented with SPA.
* @hide
*/
@@ -143,6 +162,9 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_SHORTCUT, "false");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
}
@@ -158,6 +180,9 @@ public class FeatureFlagUtils {
PERSISTENT_FLAGS.add(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE);
PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI);
+ PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_SHORTCUT);
+ PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
+ PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD);
}
/**
diff --git a/core/java/android/view/HandwritingDelegateConfiguration.java b/core/java/android/view/HandwritingDelegateConfiguration.java
new file mode 100644
index 000000000000..524bb4ca68ab
--- /dev/null
+++ b/core/java/android/view/HandwritingDelegateConfiguration.java
@@ -0,0 +1,74 @@
+/*
+ * 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.view;
+
+import android.annotation.IdRes;
+import android.annotation.NonNull;
+
+/**
+ * Configuration for a view to act as a handwriting initiation delegate. This allows handwriting
+ * mode for a delegator editor view to be initiated by stylus movement on the delegate view.
+ *
+ * <p>If a stylus {@link MotionEvent} occurs within the delegate view's bounds, the callback
+ * returned by {@link #getInitiationCallback()} will be called. The callback implementation is
+ * expected to show and focus the delegator editor view. If a view with identifier matching {@link
+ * #getDelegatorViewId()} creates an input connection while the same stylus {@link MotionEvent}
+ * sequence is ongoing, handwriting mode will be initiated for that view.
+ *
+ * <p>A common use case is a custom view which looks like a text editor but does not actually
+ * support text editing itself, and clicking on the custom view causes an EditText to be shown. To
+ * support handwriting initiation in this case, {@link View#setHandwritingDelegateConfiguration} can
+ * be called on the custom view to configure it as a delegate, and set the EditText as the delegator
+ * by passing the EditText's identifier as the {@code delegatorViewId}. The {@code
+ * initiationCallback} implementation is typically the same as the click listener implementation
+ * which shows the EditText.
+ */
+public class HandwritingDelegateConfiguration {
+ @IdRes private final int mDelegatorViewId;
+ @NonNull private final Runnable mInitiationCallback;
+
+ /**
+ * Constructs a HandwritingDelegateConfiguration instance.
+ *
+ * @param delegatorViewId identifier of the delegator editor view for which handwriting mode
+ * should be initiated
+ * @param initiationCallback callback called when a stylus {@link MotionEvent} occurs within
+ * this view's bounds
+ */
+ public HandwritingDelegateConfiguration(
+ @IdRes int delegatorViewId, @NonNull Runnable initiationCallback) {
+ mDelegatorViewId = delegatorViewId;
+ mInitiationCallback = initiationCallback;
+ }
+
+ /**
+ * Returns the identifier of the delegator editor view for which handwriting mode should be
+ * initiated.
+ */
+ public int getDelegatorViewId() {
+ return mDelegatorViewId;
+ }
+
+ /**
+ * Returns the callback which should be called when a stylus {@link MotionEvent} occurs within
+ * the delegate view's bounds.
+ */
+ @NonNull
+ public Runnable getInitiationCallback() {
+ return mInitiationCallback;
+ }
+}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index a0a07b30662f..2e4073e9cfd0 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
@@ -161,6 +162,15 @@ public class HandwritingInitiator {
if (candidateView != null) {
if (candidateView == getConnectedView()) {
startHandwriting(candidateView);
+ } else if (candidateView.getHandwritingDelegateConfiguration() != null) {
+ mState.mDelegatorViewId =
+ candidateView
+ .getHandwritingDelegateConfiguration()
+ .getDelegatorViewId();
+ candidateView
+ .getHandwritingDelegateConfiguration()
+ .getInitiationCallback()
+ .run();
} else {
if (candidateView.getRevealOnFocusHint()) {
candidateView.setRevealOnFocusHint(false);
@@ -259,8 +269,10 @@ public class HandwritingInitiator {
}
final Rect handwritingArea = getViewHandwritingArea(connectedView);
- if (isInHandwritingArea(handwritingArea, mState.mStylusDownX,
- mState.mStylusDownY, connectedView)) {
+ if ((mState.mDelegatorViewId != View.NO_ID
+ && mState.mDelegatorViewId == connectedView.getId())
+ || isInHandwritingArea(
+ handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) {
startHandwriting(connectedView);
} else {
mState.mShouldInitHandwriting = false;
@@ -287,6 +299,11 @@ public class HandwritingInitiator {
if (!view.isAutoHandwritingEnabled()) {
return false;
}
+ // The view may be a handwriting initiation delegate, in which case it is not the editor
+ // view for which handwriting would be started. However, in almost all cases, the return
+ // values of View#isStylusHandwritingAvailable will be the same for the delegate view and
+ // the delegator editor view. So the delegate view can be used to decide whether handwriting
+ // should be triggered.
return view.isStylusHandwritingAvailable();
}
@@ -473,6 +490,13 @@ public class HandwritingInitiator {
* built InputConnection.
*/
private boolean mExceedHandwritingSlop;
+ /**
+ * If the current ongoing stylus MotionEvent sequence started over a handwriting initiation
+ * delegate view, then this is the view identifier of the corresponding delegator view. If
+ * the delegator view creates an input connection while the MotionEvent sequence is still
+ * ongoing, then handwriting mode will be initiated for the delegator view.
+ */
+ @IdRes private int mDelegatorViewId = View.NO_ID;
/** The pointer id of the stylus pointer that is being tracked. */
private final int mStylusPointerId;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7b6ebf7cd9b8..49d9e67ce51d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5063,6 +5063,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private boolean mHoveringTouchDelegate = false;
/**
+ * Configuration for this view to act as a handwriting initiation delegate. This allows
+ * handwriting mode for a delegator editor view to be initiated by stylus movement on this
+ * delegate view.
+ */
+ private HandwritingDelegateConfiguration mHandwritingDelegateConfiguration;
+
+ /**
* Solid color to use as a background when creating the drawing cache. Enables
* the cache to use 16 bit bitmaps instead of 32 bit.
*/
@@ -12255,6 +12262,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Configures this view to act as a handwriting initiation delegate. This allows handwriting
+ * mode for a delegator editor view to be initiated by stylus movement on this delegate view.
+ *
+ * <p>If {@code null} is passed, this view will no longer act as a handwriting initiation
+ * delegate.
+ */
+ public void setHandwritingDelegateConfiguration(
+ @Nullable HandwritingDelegateConfiguration configuration) {
+ mHandwritingDelegateConfiguration = configuration;
+ if (configuration != null) {
+ setHandwritingArea(new Rect(0, 0, getWidth(), getHeight()));
+ }
+ }
+
+ /**
+ * If this view has been configured as a handwriting initiation delegate, returns the delegate
+ * configuration.
+ */
+ @Nullable
+ public HandwritingDelegateConfiguration getHandwritingDelegateConfiguration() {
+ return mHandwritingDelegateConfiguration;
+ }
+
+ /**
* Gets the coordinates of this view in the coordinate space of the
* {@link Surface} that contains the view.
*
@@ -24205,7 +24236,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
rebuildOutline();
- if (onCheckIsTextEditor()) {
+ if (onCheckIsTextEditor() || mHandwritingDelegateConfiguration != null) {
setHandwritingArea(new Rect(0, 0, newWidth, newHeight));
}
}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 7d268a925f60..d6d7339b602c 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -16,11 +16,14 @@
package android.view.inputmethod;
+import static android.view.inputmethod.TextBoundsInfoResult.CODE_UNSUPPORTED;
+
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.RectF;
import android.inputmethodservice.InputMethodService;
import android.os.Bundle;
import android.os.Handler;
@@ -32,7 +35,9 @@ import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import java.util.function.IntConsumer;
/**
@@ -1205,6 +1210,40 @@ public interface InputConnection {
return false;
}
+
+ /**
+ * Called by input method to request the {@link TextBoundsInfo} for a range of text which is
+ * covered by or in vicinity of the given {@code RectF}. It can be used as a supplementary
+ * method to implement the handwriting gesture API -
+ * {@link #performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)}.
+ *
+ * <p><strong>Editor authors</strong>: It's preferred that the editor returns a
+ * {@link TextBoundsInfo} of all the text lines whose bounds intersect with the given
+ * {@code rectF}.
+ * </p>
+ *
+ * <p><strong>IME authors</strong>: This method is expensive when the text is long. Please
+ * consider that both the text bounds computation and IPC round-trip to send the data are time
+ * consuming. It's preferable to only request text bounds in smaller areas.
+ * </p>
+ *
+ * @param rectF the interested area where the text bounds are requested, in the screen
+ * coordinates.
+ * @param executor the executor to run the callback.
+ * @param consumer the callback invoked by editor to return the result. It must return a
+ * non-null object.
+ *
+ * @see TextBoundsInfo
+ * @see android.view.inputmethod.TextBoundsInfoResult
+ */
+ default void requestTextBoundsInfo(
+ @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<TextBoundsInfoResult> consumer) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(consumer);
+ executor.execute(() -> consumer.accept(new TextBoundsInfoResult(CODE_UNSUPPORTED)));
+ }
+
/**
* Called by the system to enable application developers to specify a dedicated thread on which
* {@link InputConnection} methods are called back.
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 56beddf2ef38..7af96b60938c 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -20,6 +20,7 @@ import android.annotation.CallbackExecutor;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.RectF;
import android.os.Bundle;
import android.os.Handler;
import android.view.KeyEvent;
@@ -27,6 +28,7 @@ import android.view.KeyEvent;
import com.android.internal.util.Preconditions;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import java.util.function.IntConsumer;
/**
@@ -347,6 +349,17 @@ public class InputConnectionWrapper implements InputConnection {
* @throws NullPointerException if the target is {@code null}.
*/
@Override
+ public void requestTextBoundsInfo(
+ @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<TextBoundsInfoResult> consumer) {
+ mTarget.requestTextBoundsInfo(rectF, executor, consumer);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ @Override
public Handler getHandler() {
return mTarget.getHandler();
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 53d77e146d89..1697bf8bcf7b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1902,8 +1902,11 @@ public final class InputMethodManager {
* a result receiver: explicitly request that the current input method's
* soft input area be shown to the user, if needed.
*
- * @param view The currently focused view, which would like to receive
- * soft keyboard input.
+ * @param view The currently focused view, which would like to receive soft keyboard input.
+ * Note that this view is only considered focused here if both it itself has
+ * {@link View#isFocused view focus}, and its containing window has
+ * {@link View#hasWindowFocus window focus}. Otherwise the call fails and
+ * returns {@code false}.
* @param flags Provides additional operating flags. Currently may be
* 0 or have the {@link #SHOW_IMPLICIT} bit set.
*/
@@ -1965,8 +1968,11 @@ public final class InputMethodManager {
* can be garbage collected regardless of the lifetime of
* {@link ResultReceiver}.
*
- * @param view The currently focused view, which would like to receive
- * soft keyboard input.
+ * @param view The currently focused view, which would like to receive soft keyboard input.
+ * Note that this view is only considered focused here if both it itself has
+ * {@link View#isFocused view focus}, and its containing window has
+ * {@link View#hasWindowFocus window focus}. Otherwise the call fails and
+ * returns {@code false}.
* @param flags Provides additional operating flags. Currently may be
* 0 or have the {@link #SHOW_IMPLICIT} bit set.
* @param resultReceiver If non-null, this will be called by the IME when
diff --git a/core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl
new file mode 100644
index 000000000000..ffadf820325e
--- /dev/null
+++ b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.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.view.inputmethod;
+
+parcelable ParcelableHandwritingGesture;
diff --git a/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java
new file mode 100644
index 000000000000..e4066fcfd61d
--- /dev/null
+++ b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A generic container of parcelable {@link HandwritingGesture}.
+ *
+ * @hide
+ */
+public final class ParcelableHandwritingGesture implements Parcelable {
+ @NonNull
+ private final HandwritingGesture mGesture;
+ @NonNull
+ private final Parcelable mGestureAsParcelable;
+
+ private ParcelableHandwritingGesture(@NonNull HandwritingGesture gesture) {
+ mGesture = gesture;
+ // For fail-fast.
+ mGestureAsParcelable = (Parcelable) gesture;
+ }
+
+ /**
+ * Creates {@link ParcelableHandwritingGesture} from {@link HandwritingGesture}, which also
+ * implements {@link Parcelable}.
+ *
+ * @param gesture {@link HandwritingGesture} object to be stored.
+ * @return {@link ParcelableHandwritingGesture} to be stored in {@link Parcel}.
+ */
+ @NonNull
+ public static ParcelableHandwritingGesture of(@NonNull HandwritingGesture gesture) {
+ return new ParcelableHandwritingGesture(Objects.requireNonNull(gesture));
+ }
+
+ /**
+ * @return {@link HandwritingGesture} object stored in this container.
+ */
+ @NonNull
+ public HandwritingGesture get() {
+ return mGesture;
+ }
+
+ private static HandwritingGesture createFromParcelInternal(
+ @HandwritingGesture.GestureType int gestureType, @NonNull Parcel parcel) {
+ switch (gestureType) {
+ case HandwritingGesture.GESTURE_TYPE_NONE:
+ throw new UnsupportedOperationException("GESTURE_TYPE_NONE is not supported");
+ case HandwritingGesture.GESTURE_TYPE_SELECT:
+ return SelectGesture.CREATOR.createFromParcel(parcel);
+ case HandwritingGesture.GESTURE_TYPE_SELECT_RANGE:
+ return SelectRangeGesture.CREATOR.createFromParcel(parcel);
+ case HandwritingGesture.GESTURE_TYPE_INSERT:
+ return InsertGesture.CREATOR.createFromParcel(parcel);
+ case HandwritingGesture.GESTURE_TYPE_DELETE:
+ return DeleteGesture.CREATOR.createFromParcel(parcel);
+ case HandwritingGesture.GESTURE_TYPE_DELETE_RANGE:
+ return DeleteRangeGesture.CREATOR.createFromParcel(parcel);
+ case HandwritingGesture.GESTURE_TYPE_JOIN_OR_SPLIT:
+ return JoinOrSplitGesture.CREATOR.createFromParcel(parcel);
+ case HandwritingGesture.GESTURE_TYPE_REMOVE_SPACE:
+ return RemoveSpaceGesture.CREATOR.createFromParcel(parcel);
+ default:
+ throw new UnsupportedOperationException("Unknown type=" + gestureType);
+ }
+ }
+
+ public static final Creator<ParcelableHandwritingGesture> CREATOR = new Parcelable.Creator<>() {
+ @Override
+ public ParcelableHandwritingGesture createFromParcel(Parcel in) {
+ final int gestureType = in.readInt();
+ return new ParcelableHandwritingGesture(createFromParcelInternal(gestureType, in));
+ }
+
+ @Override
+ public ParcelableHandwritingGesture[] newArray(int size) {
+ return new ParcelableHandwritingGesture[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return mGestureAsParcelable.describeContents();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mGesture.getGestureType());
+ mGestureAsParcelable.writeToParcel(dest, flags);
+ }
+}
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index f2b70997de63..ead79245609a 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -28,6 +28,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.RectF;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -982,62 +983,9 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Dispatching(cancellable = true)
@Override
- public void performHandwritingSelectGesture(
- InputConnectionCommandHeader header, SelectGesture gesture,
+ public void performHandwritingGesture(
+ InputConnectionCommandHeader header, ParcelableHandwritingGesture gestureContainer,
ResultReceiver resultReceiver) {
- performHandwritingGestureInternal(header, gesture, resultReceiver);
- }
-
- @Dispatching(cancellable = true)
- @Override
- public void performHandwritingSelectRangeGesture(
- InputConnectionCommandHeader header, SelectRangeGesture gesture,
- ResultReceiver resultReceiver) {
- performHandwritingGestureInternal(header, gesture, resultReceiver);
- }
-
- @Dispatching(cancellable = true)
- @Override
- public void performHandwritingInsertGesture(
- InputConnectionCommandHeader header, InsertGesture gesture,
- ResultReceiver resultReceiver) {
- performHandwritingGestureInternal(header, gesture, resultReceiver);
- }
-
- @Dispatching(cancellable = true)
- @Override
- public void performHandwritingDeleteGesture(
- InputConnectionCommandHeader header, DeleteGesture gesture,
- ResultReceiver resultReceiver) {
- performHandwritingGestureInternal(header, gesture, resultReceiver);
- }
-
- @Dispatching(cancellable = true)
- @Override
- public void performHandwritingDeleteRangeGesture(
- InputConnectionCommandHeader header, DeleteRangeGesture gesture,
- ResultReceiver resultReceiver) {
- performHandwritingGestureInternal(header, gesture, resultReceiver);
- }
-
- @Dispatching(cancellable = true)
- @Override
- public void performHandwritingRemoveSpaceGesture(
- InputConnectionCommandHeader header, RemoveSpaceGesture gesture,
- ResultReceiver resultReceiver) {
- performHandwritingGestureInternal(header, gesture, resultReceiver);
- }
-
- @Dispatching(cancellable = true)
- @Override
- public void performHandwritingJoinOrSplitGesture(
- InputConnectionCommandHeader header, JoinOrSplitGesture gesture,
- ResultReceiver resultReceiver) {
- performHandwritingGestureInternal(header, gesture, resultReceiver);
- }
-
- private <T extends HandwritingGesture> void performHandwritingGestureInternal(
- InputConnectionCommandHeader header, T gesture, ResultReceiver resultReceiver) {
dispatchWithTracing("performHandwritingGesture", () -> {
if (header.mSessionId != mCurrentSessionId.get()) {
if (resultReceiver != null) {
@@ -1059,7 +1007,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
// TODO(210039666): implement Cleaner to return HANDWRITING_GESTURE_RESULT_UNKNOWN if
// editor doesn't return any type.
ic.performHandwritingGesture(
- gesture,
+ gestureContainer.get(),
resultReceiver != null ? Runnable::run : null,
resultReceiver != null
? (resultCode) -> resultReceiver.send(resultCode, null /* resultData */)
@@ -1147,6 +1095,36 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Dispatching(cancellable = true)
@Override
+ public void requestTextBoundsInfo(
+ InputConnectionCommandHeader header, RectF rectF,
+ @NonNull ResultReceiver resultReceiver) {
+ dispatchWithTracing("requestTextBoundsInfo", () -> {
+ if (header.mSessionId != mCurrentSessionId.get()) {
+ resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null);
+ return; // cancelled
+ }
+ InputConnection ic = getInputConnection();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "requestTextBoundsInfo on inactive InputConnection");
+ resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null);
+ return;
+ }
+
+ ic.requestTextBoundsInfo(
+ rectF,
+ Runnable::run,
+ (textBoundsInfoResult) -> {
+ final int resultCode = textBoundsInfoResult.getResultCode();
+ final TextBoundsInfo textBoundsInfo =
+ textBoundsInfoResult.getTextBoundsInfo();
+ resultReceiver.send(resultCode,
+ textBoundsInfo == null ? null : textBoundsInfo.toBundle());
+ });
+ });
+ }
+
+ @Dispatching(cancellable = true)
+ @Override
public void commitContent(InputConnectionCommandHeader header,
InputContentInfo inputContentInfo, int flags, Bundle opts,
AndroidFuture future /* T=Boolean */) {
diff --git a/core/java/android/view/inputmethod/TextBoundsInfo.java b/core/java/android/view/inputmethod/TextBoundsInfo.java
new file mode 100644
index 000000000000..4e87405f0137
--- /dev/null
+++ b/core/java/android/view/inputmethod/TextBoundsInfo.java
@@ -0,0 +1,844 @@
+/*
+ * 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.view.inputmethod;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.SegmentFinder;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * The text bounds information of a slice of text in the editor.
+ *
+ * <p> This class provides IME the layout information of the text within the range from
+ * {@link #getStart()} to {@link #getEnd()}. It's intended to be used by IME as a supplementary API
+ * to support handwriting gestures.
+ * </p>
+ */
+public final class TextBoundsInfo implements Parcelable {
+ /**
+ * The flag indicating that the character is a whitespace.
+ *
+ * @see Builder#setCharacterFlags(int[])
+ * @see #getCharacterFlags(int)
+ */
+ public static final int FLAG_CHARACTER_WHITESPACE = 1;
+
+ /**
+ * The flag indicating that the character is a linefeed character.
+ *
+ * @see Builder#setCharacterFlags(int[])
+ * @see #getCharacterFlags(int)
+ */
+ public static final int FLAG_CHARACTER_LINEFEED = 1 << 1;
+
+ /**
+ * The flag indicating that the character is a punctuation.
+ *
+ * @see Builder#setCharacterFlags(int[])
+ * @see #getCharacterFlags(int)
+ */
+ public static final int FLAG_CHARACTER_PUNCTUATION = 1 << 2;
+
+ /**
+ * The flag indicating that the line this character belongs to has RTL line direction. It's
+ * required that all characters in the same line must have the same direction.
+ *
+ * @see Builder#setCharacterFlags(int[])
+ * @see #getCharacterFlags(int)
+ */
+ public static final int FLAG_LINE_IS_RTL = 1 << 3;
+
+
+ /** @hide */
+ @IntDef(prefix = "FLAG_", flag = true, value = {
+ FLAG_CHARACTER_WHITESPACE,
+ FLAG_CHARACTER_LINEFEED,
+ FLAG_CHARACTER_PUNCTUATION,
+ FLAG_LINE_IS_RTL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CharacterFlags {}
+
+ /** All the valid flags. */
+ private static final int KNOWN_CHARACTER_FLAGS = FLAG_CHARACTER_WHITESPACE
+ | FLAG_CHARACTER_LINEFEED | FLAG_CHARACTER_PUNCTUATION | FLAG_LINE_IS_RTL;
+
+ /**
+ * The amount of shift to get the character's BiDi level from the internal character flags.
+ */
+ private static final int BIDI_LEVEL_SHIFT = 19;
+
+ /**
+ * The mask used to get the character's BiDi level from the internal character flags.
+ */
+ private static final int BIDI_LEVEL_MASK = 0x7F << BIDI_LEVEL_SHIFT;
+
+ /**
+ * The flag indicating that the character at the index is the start of a line segment.
+ * This flag is only used internally to serialize the {@link SegmentFinder}.
+ *
+ * @see #writeToParcel(Parcel, int)
+ */
+ private static final int FLAG_LINE_SEGMENT_START = 1 << 31;
+
+ /**
+ * The flag indicating that the character at the index is the end of a line segment.
+ * This flag is only used internally to serialize the {@link SegmentFinder}.
+ *
+ * @see #writeToParcel(Parcel, int)
+ */
+ private static final int FLAG_LINE_SEGMENT_END = 1 << 30;
+
+ /**
+ * The flag indicating that the character at the index is the start of a word segment.
+ * This flag is only used internally to serialize the {@link SegmentFinder}.
+ *
+ * @see #writeToParcel(Parcel, int)
+ */
+ private static final int FLAG_WORD_SEGMENT_START = 1 << 29;
+
+ /**
+ * The flag indicating that the character at the index is the end of a word segment.
+ * This flag is only used internally to serialize the {@link SegmentFinder}.
+ *
+ * @see #writeToParcel(Parcel, int)
+ */
+ private static final int FLAG_WORD_SEGMENT_END = 1 << 28;
+
+ /**
+ * The flag indicating that the character at the index is the start of a grapheme segment.
+ * It's only used internally to serialize the {@link SegmentFinder}.
+ *
+ * @see #writeToParcel(Parcel, int)
+ */
+ private static final int FLAG_GRAPHEME_SEGMENT_START = 1 << 27;
+
+ /**
+ * The flag indicating that the character at the index is the end of a grapheme segment.
+ * It's only used internally to serialize the {@link SegmentFinder}.
+ *
+ * @see #writeToParcel(Parcel, int)
+ */
+ private static final int FLAG_GRAPHEME_SEGMENT_END = 1 << 26;
+
+ private final int mStart;
+ private final int mEnd;
+ private final float[] mMatrixValues;
+ private final float[] mCharacterBounds;
+ /**
+ * The array that encodes character and BiDi levels. They are stored together to save memory
+ * space, and it's easier during serialization.
+ */
+ private final int[] mInternalCharacterFlags;
+ private final SegmentFinder mLineSegmentFinder;
+ private final SegmentFinder mWordSegmentFinder;
+ private final SegmentFinder mGraphemeSegmentFinder;
+
+ /**
+ * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation
+ * matrix that is to be applied other positional data in this class.
+ *
+ * @return a new instance (copy) of the transformation matrix.
+ */
+ @NonNull
+ public Matrix getMatrix() {
+ final Matrix matrix = new Matrix();
+ matrix.setValues(mMatrixValues);
+ return matrix;
+ }
+
+ /**
+ * Returns the index of the first character whose bounds information is available in this
+ * {@link TextBoundsInfo}, inclusive.
+ *
+ * @see Builder#setStartAndEnd(int, int)
+ */
+ public int getStart() {
+ return mStart;
+ }
+
+ /**
+ * Returns the index of the last character whose bounds information is available in this
+ * {@link TextBoundsInfo}, exclusive.
+ *
+ * @see Builder#setStartAndEnd(int, int)
+ */
+ public int getEnd() {
+ return mEnd;
+ }
+
+ /**
+ * Return the bounds of the character at the given {@code index}, in the coordinates of the
+ * editor.
+ *
+ * @param index the index of the queried character.
+ * @return the bounding box of the queried character.
+ *
+ * @throws IndexOutOfBoundsException if the given {@code index} is out of the range from
+ * the {@code start} to the {@code end}.
+ */
+ @NonNull
+ public RectF getCharacterBounds(int index) {
+ if (index < mStart || index >= mEnd) {
+ throw new IndexOutOfBoundsException("Index is out of the bounds of "
+ + "[" + mStart + ", " + mEnd + ").");
+ }
+ final int offset = 4 * (index - mStart);
+ return new RectF(mCharacterBounds[offset], mCharacterBounds[offset + 1],
+ mCharacterBounds[offset + 2], mCharacterBounds[offset + 3]);
+ }
+
+ /**
+ * Return the flags associated with the character at the given {@code index}.
+ * The flags contain the following information:
+ * <ul>
+ * <li>The {@link #FLAG_CHARACTER_WHITESPACE} flag, indicating the character is a
+ * whitespace. </li>
+ * <li>The {@link #FLAG_CHARACTER_LINEFEED} flag, indicating the character is a
+ * linefeed. </li>
+ * <li>The {@link #FLAG_CHARACTER_PUNCTUATION} flag, indicating the character is a
+ * punctuation. </li>
+ * <li>The {@link #FLAG_LINE_IS_RTL} flag, indicating the line this character belongs to
+ * has RTL line direction. All characters in the same line must have the same line
+ * direction. Check {@link #getLineSegmentFinder()} for more information of
+ * line boundaries. </li>
+ * </ul>
+ *
+ * @param index the index of the queried character.
+ * @return the flags associated with the queried character.
+ *
+ * @throws IndexOutOfBoundsException if the given {@code index} is out of the range from
+ * the {@code start} to the {@code end}.
+ *
+ * @see #FLAG_CHARACTER_WHITESPACE
+ * @see #FLAG_CHARACTER_LINEFEED
+ * @see #FLAG_CHARACTER_PUNCTUATION
+ * @see #FLAG_LINE_IS_RTL
+ */
+ @CharacterFlags
+ public int getCharacterFlags(int index) {
+ if (index < mStart || index >= mEnd) {
+ throw new IndexOutOfBoundsException("Index is out of the bounds of "
+ + "[" + mStart + ", " + mEnd + ").");
+ }
+ final int offset = index - mStart;
+ return mInternalCharacterFlags[offset] & KNOWN_CHARACTER_FLAGS;
+ }
+
+ /**
+ * The BiDi level of the character at the given {@code index}. <br/>
+ * BiDi level is defined by
+ * <a href="https://unicode.org/reports/tr9/#Basic_Display_Algorithm" >the unicode
+ * bidirectional algorithm </a>. One can determine whether a character's direction is
+ * right-to-left (RTL) or left-to-right (LTR) by checking the last bit of the BiDi level.
+ * If it's 1, the character is RTL, otherwise the character is LTR. The BiDi level of a
+ * character must be in the range of [0, 125].
+ *
+ * @param index the index of the queried character.
+ * @return the BiDi level of the character, which is an integer in the range of [0, 125].
+ * @throws IndexOutOfBoundsException if the given {@code index} is out of the range from
+ * the {@code start} to the {@code end}.
+ *
+ * @see Builder#setCharacterBidiLevel(int[])
+ */
+ @IntRange(from = 0, to = 125)
+ public int getCharacterBidiLevel(int index) {
+ if (index < mStart || index >= mEnd) {
+ throw new IndexOutOfBoundsException("Index is out of the bounds of "
+ + "[" + mStart + ", " + mEnd + ").");
+ }
+ final int offset = index - mStart;
+ return (mInternalCharacterFlags[offset] & BIDI_LEVEL_MASK) >> BIDI_LEVEL_SHIFT;
+ }
+
+ /**
+ * Returns the {@link SegmentFinder} that locates the word boundaries.
+ *
+ * @see Builder#setWordSegmentFinder(SegmentFinder)
+ */
+ @NonNull
+ public SegmentFinder getWordSegmentFinder() {
+ return mWordSegmentFinder;
+ }
+
+ /**
+ * Returns the {@link SegmentFinder} that locates the grapheme boundaries.
+ *
+ * @see Builder#setGraphemeSegmentFinder(SegmentFinder)
+ */
+ @NonNull
+ public SegmentFinder getGraphemeSegmentFinder() {
+ return mGraphemeSegmentFinder;
+ }
+
+ /**
+ * Returns the {@link SegmentFinder} that locates the line boundaries.
+ *
+ * @see Builder#setLineSegmentFinder(SegmentFinder)
+ */
+ @NonNull
+ public SegmentFinder getLineSegmentFinder() {
+ return mLineSegmentFinder;
+ }
+
+ /**
+ * Describe the kinds of special objects contained in this Parcelable
+ * instance's marshaled representation. For example, if the object will
+ * include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
+ * the return value of this method must include the
+ * {@link #CONTENTS_FILE_DESCRIPTOR} bit.
+ *
+ * @return a bitmask indicating the set of special object types marshaled
+ * by this Parcelable object instance.
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Flatten this object in to a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mStart);
+ dest.writeInt(mEnd);
+ dest.writeFloatArray(mMatrixValues);
+ dest.writeFloatArray(mCharacterBounds);
+
+ // The end can also be a break position. We need an extra space to encode the breaks.
+ final int[] encodedFlags = Arrays.copyOf(mInternalCharacterFlags, mEnd - mStart + 1);
+ encodeSegmentFinder(encodedFlags, FLAG_GRAPHEME_SEGMENT_START, FLAG_GRAPHEME_SEGMENT_END,
+ mStart, mEnd, mGraphemeSegmentFinder);
+ encodeSegmentFinder(encodedFlags, FLAG_WORD_SEGMENT_START, FLAG_WORD_SEGMENT_END, mStart,
+ mEnd, mWordSegmentFinder);
+ encodeSegmentFinder(encodedFlags, FLAG_LINE_SEGMENT_START, FLAG_LINE_SEGMENT_END, mStart,
+ mEnd, mLineSegmentFinder);
+ dest.writeIntArray(encodedFlags);
+ }
+
+ private TextBoundsInfo(Parcel source) {
+ mStart = source.readInt();
+ mEnd = source.readInt();
+ mMatrixValues = Objects.requireNonNull(source.createFloatArray());
+ mCharacterBounds = Objects.requireNonNull(source.createFloatArray());
+ final int[] encodedFlags = Objects.requireNonNull(source.createIntArray());
+
+ mGraphemeSegmentFinder = decodeSegmentFinder(encodedFlags, FLAG_GRAPHEME_SEGMENT_START,
+ FLAG_GRAPHEME_SEGMENT_END, mStart, mEnd);
+ mWordSegmentFinder = decodeSegmentFinder(encodedFlags, FLAG_WORD_SEGMENT_START,
+ FLAG_WORD_SEGMENT_END, mStart, mEnd);
+ mLineSegmentFinder = decodeSegmentFinder(encodedFlags, FLAG_LINE_SEGMENT_START,
+ FLAG_LINE_SEGMENT_END, mStart, mEnd);
+
+ final int length = mEnd - mStart;
+ final int flagsMask = KNOWN_CHARACTER_FLAGS | BIDI_LEVEL_MASK;
+ mInternalCharacterFlags = new int[length];
+ for (int i = 0; i < length; ++i) {
+ // Remove the flags used to encoded segment boundaries.
+ mInternalCharacterFlags[i] = encodedFlags[i] & flagsMask;
+ }
+ }
+
+ private TextBoundsInfo(Builder builder) {
+ mStart = builder.mStart;
+ mEnd = builder.mEnd;
+ mMatrixValues = Arrays.copyOf(builder.mMatrixValues, 9);
+ final int length = mEnd - mStart;
+ mCharacterBounds = Arrays.copyOf(builder.mCharacterBounds, 4 * length);
+ // Store characterFlags and characterBidiLevels to save memory.
+ mInternalCharacterFlags = new int[length];
+ for (int index = 0; index < length; ++index) {
+ mInternalCharacterFlags[index] = builder.mCharacterFlags[index]
+ | (builder.mCharacterBidiLevels[index] << BIDI_LEVEL_SHIFT);
+ }
+ mGraphemeSegmentFinder = builder.mGraphemeSegmentFinder;
+ mWordSegmentFinder = builder.mWordSegmentFinder;
+ mLineSegmentFinder = builder.mLineSegmentFinder;
+ }
+
+ /**
+ * The CREATOR to make this class Parcelable.
+ */
+ @NonNull
+ public static final Parcelable.Creator<TextBoundsInfo> CREATOR = new Creator<TextBoundsInfo>() {
+ @Override
+ public TextBoundsInfo createFromParcel(Parcel source) {
+ return new TextBoundsInfo(source);
+ }
+
+ @Override
+ public TextBoundsInfo[] newArray(int size) {
+ return new TextBoundsInfo[size];
+ }
+ };
+
+ private static final String TEXT_BOUNDS_INFO_KEY = "android.view.inputmethod.TextBoundsInfo";
+
+ /**
+ * Store the {@link TextBoundsInfo} into a {@link Bundle}. This method is used by
+ * {@link RemoteInputConnectionImpl} to transfer the {@link TextBoundsInfo} from the editor
+ * to IME.
+ *
+ * @see TextBoundsInfoResult
+ * @see InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)
+ * @hide
+ */
+ @NonNull
+ public Bundle toBundle() {
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(TEXT_BOUNDS_INFO_KEY, this);
+ return bundle;
+
+ }
+
+ /** @hide */
+ @Nullable
+ public static TextBoundsInfo createFromBundle(@Nullable Bundle bundle) {
+ if (bundle == null) return null;
+ return bundle.getParcelable(TEXT_BOUNDS_INFO_KEY, TextBoundsInfo.class);
+ }
+
+ /**
+ * The builder class to create a {@link TextBoundsInfo} object.
+ */
+ public static final class Builder {
+ private final float[] mMatrixValues = new float[9];
+ private boolean mMatrixInitialized;
+ private int mStart;
+ private int mEnd;
+ private float[] mCharacterBounds;
+ private int[] mCharacterFlags;
+ private int[] mCharacterBidiLevels;
+ private SegmentFinder mLineSegmentFinder;
+ private SegmentFinder mWordSegmentFinder;
+ private SegmentFinder mGraphemeSegmentFinder;
+
+ /** Clear all the parameters set on this {@link Builder} to reuse it. */
+ @NonNull
+ public Builder clear() {
+ mMatrixInitialized = false;
+ mStart = -1;
+ mEnd = -1;
+ mCharacterBounds = null;
+ mCharacterFlags = null;
+ mLineSegmentFinder = null;
+ mWordSegmentFinder = null;
+ mGraphemeSegmentFinder = null;
+ return this;
+ }
+
+ /**
+ * Sets the matrix that transforms local coordinates into screen coordinates.
+ *
+ * @param matrix transformation matrix from local coordinates into screen coordinates.
+ * @throws NullPointerException if the given {@code matrix} is {@code null}.
+ */
+ @NonNull
+ public Builder setMatrix(@NonNull Matrix matrix) {
+ Objects.requireNonNull(matrix).getValues(mMatrixValues);
+ mMatrixInitialized = true;
+ return this;
+ }
+
+ /**
+ * Set the start and end index of the {@link TextBoundsInfo}. It's the range of the
+ * characters whose information is available in the {@link TextBoundsInfo}.
+ *
+ * @param start the start index of the {@link TextBoundsInfo}, inclusive.
+ * @param end the end index of the {@link TextBoundsInfo}, exclusive.
+ * @throws IllegalArgumentException if the given {@code start} or {@code end} is negative,
+ * or {@code end} is smaller than the {@code start}.
+ */
+ @NonNull
+ @SuppressWarnings("MissingGetterMatchingBuilder")
+ public Builder setStartAndEnd(@IntRange(from = 0) int start, @IntRange(from = 0) int end) {
+ Preconditions.checkArgument(start >= 0);
+ Preconditions.checkArgumentInRange(start, 0, end, "start");
+ mStart = start;
+ mEnd = end;
+ return this;
+ }
+
+ /**
+ * Set the characters bounds, in the coordinates of the editor. <br/>
+ *
+ * The given array should be divided into groups of four where each element represents
+ * left, top, right and bottom of the character bounds respectively.
+ * The bounds of the i-th character in the editor should be stored at index
+ * 4 * (i - start). The length of the given array must equal to 4 * (end - start). <br/>
+ *
+ * Sometimes multiple characters in a single grapheme are rendered as one symbol on the
+ * screen. So those characters only have one shared bounds. In this case, we recommend the
+ * editor to assign all the width to the bounds of the first character in the grapheme,
+ * and make the rest characters' bounds zero-width. <br/>
+ *
+ * For example, the string "'0xD83D' '0xDE00'" is rendered as one grapheme - a grinning face
+ * emoji. If the bounds of the grapheme is: Rect(5, 10, 15, 20), the character bounds of the
+ * string should be: [ Rect(5, 10, 15, 20), Rect(15, 10, 15, 20) ].
+ *
+ * @param characterBounds the array of the flattened character bounds.
+ * @throws NullPointerException if the given {@code characterBounds} is {@code null}.
+ */
+ @NonNull
+ public Builder setCharacterBounds(@NonNull float[] characterBounds) {
+ mCharacterBounds = Objects.requireNonNull(characterBounds);
+ return this;
+ }
+
+ /**
+ * Set the flags of the characters. The flags of the i-th character in the editor is stored
+ * at index (i - start). The length of the given array must equal to (end - start).
+ * The flags contain the following information:
+ * <ul>
+ * <li>The {@link #FLAG_CHARACTER_WHITESPACE} flag, indicating the character is a
+ * whitespace. </li>
+ * <li>The {@link #FLAG_CHARACTER_LINEFEED} flag, indicating the character is a
+ * linefeed. </li>
+ * <li>The {@link #FLAG_CHARACTER_PUNCTUATION} flag, indicating the character is a
+ * punctuation. </li>
+ * <li>The {@link #FLAG_LINE_IS_RTL} flag, indicating the line this character belongs to
+ * is RTL. All all character in the same line must have the same line direction. Check
+ * {@link #getLineSegmentFinder()} for more information of line boundaries. </li>
+ * </ul>
+ *
+ * @param characterFlags the array of the character's flags.
+ * @throws NullPointerException if the given {@code characterFlags} is {@code null}.
+ * @throws IllegalArgumentException if the given {@code characterFlags} contains invalid
+ * flags.
+ *
+ * @see #getCharacterFlags(int)
+ */
+ @NonNull
+ public Builder setCharacterFlags(@NonNull int[] characterFlags) {
+ Objects.requireNonNull(characterFlags);
+ for (int characterFlag : characterFlags) {
+ if ((characterFlag & (~KNOWN_CHARACTER_FLAGS)) != 0) {
+ throw new IllegalArgumentException("characterFlags contains invalid flags.");
+ }
+ }
+ mCharacterFlags = characterFlags;
+ return this;
+ }
+
+ /**
+ * Set the BiDi levels for the character. The bidiLevel of the i-th character in the editor
+ * is stored at index (i - start). The length of the given array must equal to
+ * (end - start). <br/>
+ *
+ * BiDi level is defined by
+ * <a href="https://unicode.org/reports/tr9/#Basic_Display_Algorithm" >the unicode
+ * bidirectional algorithm </a>. One can determine whether a character's direction is
+ * right-to-left (RTL) or left-to-right (LTR) by checking the last bit of the BiDi level.
+ * If it's 1, the character is RTL, otherwise the character is LTR. The BiDi level of a
+ * character must be in the range of [0, 125].
+ * @param characterBidiLevels the array of the character's BiDi level.
+ *
+ * @throws NullPointerException if the given {@code characterBidiLevels} is {@code null}.
+ * @throws IllegalArgumentException if the given {@code characterBidiLevels} contains an
+ * element that's out of the range [0, 125].
+ *
+ * @see #getCharacterBidiLevel(int)
+ */
+ @NonNull
+ public Builder setCharacterBidiLevel(@NonNull int[] characterBidiLevels) {
+ Objects.requireNonNull(characterBidiLevels);
+ for (int index = 0; index < characterBidiLevels.length; ++index) {
+ Preconditions.checkArgumentInRange(characterBidiLevels[index], 0, 125,
+ "bidiLevels[" + index + "]");
+ }
+ mCharacterBidiLevels = characterBidiLevels;
+ return this;
+ }
+
+ /**
+ * Set the {@link SegmentFinder} that locates the grapheme cluster boundaries. Grapheme is
+ * defined in <a href="https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries">
+ * the unicode annex #29: unicode text segmentation<a/>. It's a user-perspective character.
+ * And it's usually the minimal unit for selection, backspace, deletion etc. <br/>
+ *
+ * Please note that only the grapheme segments within the range from start to end will
+ * be available to the IME. The remaining information will be discarded during serialization
+ * for better performance.
+ *
+ * @param graphemeSegmentFinder the {@link SegmentFinder} that locates the grapheme cluster
+ * boundaries.
+ * @throws NullPointerException if the given {@code graphemeSegmentFinder} is {@code null}.
+ *
+ * @see #getGraphemeSegmentFinder()
+ * @see SegmentFinder
+ * @see SegmentFinder.DefaultSegmentFinder
+ */
+ @NonNull
+ public Builder setGraphemeSegmentFinder(@NonNull SegmentFinder graphemeSegmentFinder) {
+ mGraphemeSegmentFinder = Objects.requireNonNull(graphemeSegmentFinder);
+ return this;
+ }
+
+ /**
+ * Set the {@link SegmentFinder} that locates the word boundaries. <br/>
+ *
+ * Please note that only the word segments within the range from start to end will
+ * be available to the IME. The remaining information will be discarded during serialization
+ * for better performance.
+ * @param wordSegmentFinder set the {@link SegmentFinder} that locates the word boundaries.
+ * @throws NullPointerException if the given {@code wordSegmentFinder} is {@code null}.
+ *
+ * @see #getWordSegmentFinder()
+ * @see SegmentFinder
+ * @see SegmentFinder.DefaultSegmentFinder
+ */
+ @NonNull
+ public Builder setWordSegmentFinder(@NonNull SegmentFinder wordSegmentFinder) {
+ mWordSegmentFinder = Objects.requireNonNull(wordSegmentFinder);
+ return this;
+ }
+
+ /**
+ * Set the {@link SegmentFinder} that locates the line boundaries. Aside from the hard
+ * breaks in the text, it should also locate the soft line breaks added by the editor.
+ * It is expected that the characters within the same line is rendered on the same baseline.
+ * (Except for some text formatted as subscript and superscript.) <br/>
+ *
+ * Please note that only the line segments within the range from start to end will
+ * be available to the IME. The remaining information will be discarded during serialization
+ * for better performance.
+ * @param lineSegmentFinder set the {@link SegmentFinder} that locates the line boundaries.
+ * @throws NullPointerException if the given {@code lineSegmentFinder} is {@code null}.
+ *
+ * @see #getLineSegmentFinder()
+ * @see SegmentFinder
+ * @see SegmentFinder.DefaultSegmentFinder
+ */
+ @NonNull
+ public Builder setLineSegmentFinder(@NonNull SegmentFinder lineSegmentFinder) {
+ mLineSegmentFinder = Objects.requireNonNull(lineSegmentFinder);
+ return this;
+ }
+
+ /**
+ * Create the {@link TextBoundsInfo} using the parameters in this {@link Builder}.
+ *
+ * @throws IllegalStateException in the following conditions:
+ * <ul>
+ * <li>if the {@code start} or {@code end} is not set.</li>
+ * <li>if the {@code matrix} is not set.</li>
+ * <li>if {@code characterBounds} is not set or its length doesn't equal to
+ * 4 * ({@code end} - {@code start}).</li>
+ * <li>if the {@code characterFlags} is not set or its length doesn't equal to
+ * ({@code end} - {@code start}).</li>
+ * <li>if {@code graphemeSegmentFinder}, {@code wordSegmentFinder} or
+ * {@code lineSegmentFinder} is not set.</li>
+ * <li>if characters in the same line has inconsistent {@link #FLAG_LINE_IS_RTL}
+ * flag.</li>
+ * </ul>
+ */
+ @NonNull
+ public TextBoundsInfo build() {
+ if (mStart < 0 || mEnd < 0) {
+ throw new IllegalStateException("Start and end must be set.");
+ }
+
+ if (!mMatrixInitialized) {
+ throw new IllegalStateException("Matrix must be set.");
+ }
+
+ if (mCharacterBounds == null) {
+ throw new IllegalStateException("CharacterBounds must be set.");
+ }
+
+ if (mCharacterFlags == null) {
+ throw new IllegalStateException("CharacterFlags must be set.");
+ }
+
+ if (mCharacterBidiLevels == null) {
+ throw new IllegalStateException("CharacterBidiLevel must be set.");
+ }
+
+ if (mCharacterBounds.length != 4 * (mEnd - mStart)) {
+ throw new IllegalStateException("The length of characterBounds doesn't match the "
+ + "length of the given start and end."
+ + " Expected length: " + (4 * (mEnd - mStart))
+ + " characterBounds length: " + mCharacterBounds.length);
+ }
+ if (mCharacterFlags.length != mEnd - mStart) {
+ throw new IllegalStateException("The length of characterFlags doesn't match the "
+ + "length of the given start and end."
+ + " Expected length: " + (mEnd - mStart)
+ + " characterFlags length: " + mCharacterFlags.length);
+ }
+ if (mCharacterBidiLevels.length != mEnd - mStart) {
+ throw new IllegalStateException("The length of characterBidiLevels doesn't match"
+ + " the length of the given start and end."
+ + " Expected length: " + (mEnd - mStart)
+ + " characterFlags length: " + mCharacterBidiLevels.length);
+ }
+ if (mGraphemeSegmentFinder == null) {
+ throw new IllegalStateException("GraphemeSegmentFinder must be set.");
+ }
+ if (mWordSegmentFinder == null) {
+ throw new IllegalStateException("WordSegmentFinder must be set.");
+ }
+ if (mLineSegmentFinder == null) {
+ throw new IllegalStateException("LineSegmentFinder must be set.");
+ }
+
+ if (!isLineDirectionFlagConsistent(mCharacterFlags, mLineSegmentFinder, mStart, mEnd)) {
+ throw new IllegalStateException("characters in the same line must have the same "
+ + "FLAG_LINE_IS_RTL flag value.");
+ }
+ return new TextBoundsInfo(this);
+ }
+ }
+
+ /**
+ * Encode the segment start and end positions in {@link SegmentFinder} to a flags array.
+ *
+ * For example:
+ * Text: "A BC DE"
+ * Input:
+ * start: 2, end: 7 // substring "BC DE"
+ * SegmentFinder: segment ranges = [(2, 4), (5, 7)] // a word break iterator
+ * flags: [0x0000, 0x0000, 0x0080, 0x0000, 0x0000, 0x0000] // 0x0080 is whitespace
+ * segmentStartFlag: 0x0100
+ * segmentEndFlag: 0x0200
+ * Output:
+ * flags: [0x0100, 0x0000, 0x0280, 0x0100, 0x0000, 0x0200]
+ * The index 2 and 5 encode segment starts, the index 4 and 7 encode a segment end.
+ *
+ * @param flags the flags array to receive the results.
+ * @param segmentStartFlag the flag used to encode the segment start.
+ * @param segmentEndFlag the flag used to encode the segment end.
+ * @param start the start index of the encoded range, inclusive.
+ * @param end the end index of the encoded range, inclusive.
+ * @param segmentFinder the SegmentFinder to be encoded.
+ *
+ * @see #decodeSegmentFinder(int[], int, int, int, int)
+ */
+ private static void encodeSegmentFinder(@NonNull int[] flags, int segmentStartFlag,
+ int segmentEndFlag, int start, int end, @NonNull SegmentFinder segmentFinder) {
+ if (end - start + 1 != flags.length) {
+ throw new IllegalStateException("The given flags array must have the same length as"
+ + " the given range. flags length: " + flags.length
+ + " range: [" + start + ", " + end + "]");
+ }
+
+ int segmentEnd = segmentFinder.nextEndBoundary(start);
+ if (segmentEnd == SegmentFinder.DONE) return;
+ int segmentStart = segmentFinder.previousStartBoundary(segmentEnd);
+
+ while (segmentEnd != SegmentFinder.DONE && segmentEnd <= end) {
+ if (segmentStart >= start) {
+ flags[segmentStart - start] |= segmentStartFlag;
+ flags[segmentEnd - start] |= segmentEndFlag;
+ }
+ segmentStart = segmentFinder.nextStartBoundary(segmentStart);
+ segmentEnd = segmentFinder.nextEndBoundary(segmentEnd);
+ }
+ }
+
+ /**
+ * Decode a {@link SegmentFinder} from a flags array.
+ *
+ * For example:
+ * Text: "A BC DE"
+ * Input:
+ * start: 2, end: 7 // substring "BC DE"
+ * flags: [0x0100, 0x0000, 0x0280, 0x0100, 0x0000, 0x0200]
+ * segmentStartFlag: 0x0100
+ * segmentEndFlag: 0x0200
+ * Output:
+ * SegmentFinder: segment ranges = [(2, 4), (5, 7)]
+ *
+ * @param flags the flags array to decode the SegmentFinder.
+ * @param segmentStartFlag the flag to decode a segment start.
+ * @param segmentEndFlag the flag to decode a segment end.
+ * @param start the start index of the interested range, inclusive.
+ * @param end the end index of the interested range, inclusive.
+ *
+ * @see #encodeSegmentFinder(int[], int, int, int, int, SegmentFinder)
+ */
+ private static SegmentFinder decodeSegmentFinder(int[] flags, int segmentStartFlag,
+ int segmentEndFlag, int start, int end) {
+ if (end - start + 1 != flags.length) {
+ throw new IllegalStateException("The given flags array must have the same length as"
+ + " the given range. flags length: " + flags.length
+ + " range: [" + start + ", " + end + "]");
+ }
+ int[] breaks = ArrayUtils.newUnpaddedIntArray(10);
+ int count = 0;
+ for (int offset = 0; offset < flags.length; ++offset) {
+ if ((flags[offset] & segmentStartFlag) == segmentStartFlag) {
+ breaks = GrowingArrayUtils.append(breaks, count++, start + offset);
+ }
+ if ((flags[offset] & segmentEndFlag) == segmentEndFlag) {
+ breaks = GrowingArrayUtils.append(breaks, count++, start + offset);
+ }
+ }
+ return new SegmentFinder.DefaultSegmentFinder(Arrays.copyOf(breaks, count));
+ }
+
+ /**
+ * Check whether the {@link #FLAG_LINE_IS_RTL} is the same for characters in the same line.
+ * @return true if all characters in the same line has the same {@link #FLAG_LINE_IS_RTL} flag.
+ */
+ private static boolean isLineDirectionFlagConsistent(int[] characterFlags,
+ SegmentFinder lineSegmentFinder, int start, int end) {
+ int segmentEnd = lineSegmentFinder.nextEndBoundary(start);
+ if (segmentEnd == SegmentFinder.DONE) return true;
+ int segmentStart = lineSegmentFinder.previousStartBoundary(segmentEnd);
+
+ while (segmentStart != SegmentFinder.DONE && segmentStart < end) {
+ final int lineStart = Math.max(segmentStart, start);
+ final int lineEnd = Math.min(segmentEnd, end);
+ final boolean lineIsRtl = (characterFlags[lineStart - start] & FLAG_LINE_IS_RTL) != 0;
+ for (int index = lineStart + 1; index < lineEnd; ++index) {
+ final int flags = characterFlags[index - start];
+ final boolean characterLineIsRtl = (flags & FLAG_LINE_IS_RTL) != 0;
+ if (characterLineIsRtl != lineIsRtl) {
+ return false;
+ }
+ }
+
+ segmentStart = lineSegmentFinder.nextStartBoundary(segmentStart);
+ segmentEnd = lineSegmentFinder.nextEndBoundary(segmentEnd);
+ }
+ return true;
+ }
+}
diff --git a/core/java/android/view/inputmethod/TextBoundsInfoResult.java b/core/java/android/view/inputmethod/TextBoundsInfoResult.java
new file mode 100644
index 000000000000..62df17a3aeae
--- /dev/null
+++ b/core/java/android/view/inputmethod/TextBoundsInfoResult.java
@@ -0,0 +1,137 @@
+/*
+ * 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.view.inputmethod;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.RectF;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * The object that holds the result of the
+ * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call.
+ *
+ * @see InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)
+ */
+public final class TextBoundsInfoResult {
+ private final int mResultCode;
+ private final TextBoundsInfo mTextBoundsInfo;
+
+ /**
+ * Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the
+ * editor doesn't implement the method.
+ */
+ public static final int CODE_UNSUPPORTED = 0;
+
+ /**
+ * Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the
+ * editor successfully returns a {@link TextBoundsInfo}.
+ */
+ public static final int CODE_SUCCESS = 1;
+
+ /**
+ * Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the
+ * request failed. This result code is returned when the editor can't provide a valid
+ * {@link TextBoundsInfo}. (e.g. The editor view is not laid out.)
+ */
+ public static final int CODE_FAILED = 2;
+
+ /**
+ * Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the
+ * request is cancelled. This happens when the {@link InputConnection} is or becomes
+ * invalidated while requesting the
+ * {@link TextBoundsInfo}, for example because a new {@code InputConnection} was started, or
+ * due to {@link InputMethodManager#invalidateInput}.
+ */
+ public static final int CODE_CANCELLED = 3;
+
+ /** @hide */
+ @IntDef(prefix = { "CODE_" }, value = {
+ CODE_UNSUPPORTED,
+ CODE_SUCCESS,
+ CODE_FAILED,
+ CODE_CANCELLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {}
+
+ /**
+ * Create a {@link TextBoundsInfoResult} object with no {@link TextBoundsInfo}.
+ * The given {@code resultCode} can't be {@link #CODE_SUCCESS}.
+ * @param resultCode the result code of the
+ * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call.
+ */
+ public TextBoundsInfoResult(@ResultCode int resultCode) {
+ this(resultCode, null);
+ }
+
+ /**
+ * Create a {@link TextBoundsInfoResult} object.
+ *
+ * @param resultCode the result code of the
+ * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call.
+ * @param textBoundsInfo the returned {@link TextBoundsInfo} of the
+ * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call. It can't be
+ * null if the {@code resultCode} is {@link #CODE_SUCCESS}.
+ *
+ * @throws IllegalStateException if the resultCode is
+ * {@link #CODE_SUCCESS} but the given {@code textBoundsInfo}
+ * is null.
+ */
+ public TextBoundsInfoResult(@ResultCode int resultCode,
+ @NonNull TextBoundsInfo textBoundsInfo) {
+ if (resultCode == CODE_SUCCESS && textBoundsInfo == null) {
+ throw new IllegalStateException("TextBoundsInfo must be provided when the resultCode "
+ + "is CODE_SUCCESS.");
+ }
+ mResultCode = resultCode;
+ mTextBoundsInfo = textBoundsInfo;
+ }
+
+ /**
+ * Return the result code of the
+ * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call.
+ * Its value is one of the {@link #CODE_UNSUPPORTED}, {@link #CODE_SUCCESS},
+ * {@link #CODE_FAILED} and {@link #CODE_CANCELLED}.
+ */
+ @ResultCode
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ /**
+ * Return the {@link TextBoundsInfo} provided by the editor. It is non-null if the
+ * {@code resultCode} is {@link #CODE_SUCCESS}.
+ * Otherwise, it can be null in the following conditions:
+ * <ul>
+ * <li>the editor doesn't support
+ * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)}.</li>
+ * <li>the editor doesn't have the text bounds information at the moment. (e.g. the editor
+ * view is not laid out yet.) </li>
+ * <li> the {@link InputConnection} is or become inactive during the request. </li>
+ * <ul/>
+ */
+ @Nullable
+ public TextBoundsInfo getTextBoundsInfo() {
+ return mTextBoundsInfo;
+ }
+}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 8815ab35b671..0956a71bd92d 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -141,6 +141,10 @@ public final class TransitionInfo implements Parcelable {
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
public static final int FLAG_FIRST_CUSTOM = 1 << 17;
+ /** The change belongs to a window that won't contain activities. */
+ public static final int FLAGS_IS_NON_APP_WINDOW =
+ FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD | FLAG_IS_SYSTEM_WINDOW;
+
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_NONE,
@@ -579,11 +583,16 @@ public final class TransitionInfo implements Parcelable {
return mFlags;
}
- /** Whether the given change flags has included in this change. */
+ /** Whether this change contains any of the given change flags. */
public boolean hasFlags(@ChangeFlags int flags) {
return (mFlags & flags) != 0;
}
+ /** Whether this change contains all of the given change flags. */
+ public boolean hasAllFlags(@ChangeFlags int flags) {
+ return (mFlags & flags) == flags;
+ }
+
/**
* @return the bounds of the container before the change. It may be empty if the container
* is coming into existence.
diff --git a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
index ea5c9a33b762..81e060d8c648 100644
--- a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
+++ b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
@@ -16,20 +16,15 @@
package com.android.internal.inputmethod;
+import android.graphics.RectF;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
-import android.view.inputmethod.DeleteGesture;
-import android.view.inputmethod.DeleteRangeGesture;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputContentInfo;
-import android.view.inputmethod.InsertGesture;
-import android.view.inputmethod.JoinOrSplitGesture;
-import android.view.inputmethod.RemoveSpaceGesture;
-import android.view.inputmethod.SelectGesture;
-import android.view.inputmethod.SelectRangeGesture;
+import android.view.inputmethod.ParcelableHandwritingGesture;
import android.view.inputmethod.TextAttribute;
import com.android.internal.infra.AndroidFuture;
@@ -94,26 +89,8 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader;
void performPrivateCommand(in InputConnectionCommandHeader header, String action,
in Bundle data);
- void performHandwritingSelectGesture(in InputConnectionCommandHeader header,
- in SelectGesture gesture, in ResultReceiver resultReceiver);
-
- void performHandwritingSelectRangeGesture(in InputConnectionCommandHeader header,
- in SelectRangeGesture gesture, in ResultReceiver resultReceiver);
-
- void performHandwritingInsertGesture(in InputConnectionCommandHeader header,
- in InsertGesture gesture, in ResultReceiver resultReceiver);
-
- void performHandwritingDeleteGesture(in InputConnectionCommandHeader header,
- in DeleteGesture gesture, in ResultReceiver resultReceiver);
-
- void performHandwritingDeleteRangeGesture(in InputConnectionCommandHeader header,
- in DeleteRangeGesture gesture, in ResultReceiver resultReceiver);
-
- void performHandwritingRemoveSpaceGesture(in InputConnectionCommandHeader header,
- in RemoveSpaceGesture gesture, in ResultReceiver resultReceiver);
-
- void performHandwritingJoinOrSplitGesture(in InputConnectionCommandHeader header,
- in JoinOrSplitGesture gesture, in ResultReceiver resultReceiver);
+ void performHandwritingGesture(in InputConnectionCommandHeader header,
+ in ParcelableHandwritingGesture gesture, in ResultReceiver resultReceiver);
void setComposingRegion(in InputConnectionCommandHeader header, int start, int end);
@@ -130,6 +107,9 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader;
int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId,
in AndroidFuture future /* T=Boolean */);
+ void requestTextBoundsInfo(in InputConnectionCommandHeader header, in RectF rect,
+ in ResultReceiver resultReceiver /* T=TextBoundsInfoResult */);
+
void commitContent(in InputConnectionCommandHeader header, in InputContentInfo inputContentInfo,
int flags, in Bundle opts, in AndroidFuture future /* T=Boolean */);
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 23dd1b407b00..9733f4e8a663 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4945,6 +4945,11 @@
<!-- If face auth sends the user directly to home/last open app, or stays on keyguard -->
<bool name="config_faceAuthDismissesKeyguard">true</bool>
+ <!-- Default value for whether a SFPS device is required to be
+ {@link KeyguardUpdateMonitor#isDeviceInteractive()} for fingerprint auth
+ to unlock the device. -->
+ <bool name="config_requireScreenOnToAuthEnabled">false</bool>
+
<!-- The component name for the default profile supervisor, which can be set as a profile owner
even after user setup is complete. The defined component should be used for supervision purposes
only. The component must be part of a system app. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 476d36d0c207..9e3235b885ab 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2725,6 +2725,7 @@
<java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" />
<java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" />
<java-symbol type="bool" name="config_faceAuthDismissesKeyguard" />
+ <java-symbol type="bool" name="config_requireScreenOnToAuthEnabled" />
<!-- Face config -->
<java-symbol type="integer" name="config_faceMaxTemplatesPerUser" />
diff --git a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
new file mode 100644
index 000000000000..79aeaa39bc7c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ParcelableHandwritingGestureTest {
+
+ @Test
+ public void testCreationFailWithNullPointerException() {
+ assertThrows(NullPointerException.class, () -> ParcelableHandwritingGesture.of(null));
+ }
+
+ @Test
+ public void testInvalidTypeHeader() {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ // GESTURE_TYPE_NONE is not a supported header.
+ parcel.writeInt(HandwritingGesture.GESTURE_TYPE_NONE);
+ final Parcel initializedParcel = parcel;
+ assertThrows(UnsupportedOperationException.class,
+ () -> ParcelableHandwritingGesture.CREATOR.createFromParcel(initializedParcel));
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ }
+
+ @Test
+ public void testSelectGesture() {
+ verifyEqualityAfterUnparcel(new SelectGesture.Builder()
+ .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+ .setSelectionArea(new RectF(1, 2, 3, 4))
+ .setFallbackText("")
+ .build());
+ }
+
+ @Test
+ public void testSelectRangeGesture() {
+ verifyEqualityAfterUnparcel(new SelectRangeGesture.Builder()
+ .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+ .setSelectionStartArea(new RectF(1, 2, 3, 4))
+ .setSelectionEndArea(new RectF(5, 6, 7, 8))
+ .setFallbackText("")
+ .build());
+ }
+
+ @Test
+ public void testInsertGestureGesture() {
+ verifyEqualityAfterUnparcel(new InsertGesture.Builder()
+ .setTextToInsert("text")
+ .setInsertionPoint(new PointF(1, 1)).setFallbackText("")
+ .build());
+ }
+
+ @Test
+ public void testDeleteGestureGesture() {
+ verifyEqualityAfterUnparcel(new DeleteGesture.Builder()
+ .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+ .setDeletionArea(new RectF(1, 2, 3, 4))
+ .setFallbackText("")
+ .build());
+ }
+
+ @Test
+ public void testDeleteRangeGestureGesture() {
+ verifyEqualityAfterUnparcel(new DeleteRangeGesture.Builder()
+ .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+ .setDeletionStartArea(new RectF(1, 2, 3, 4))
+ .setDeletionEndArea(new RectF(5, 6, 7, 8))
+ .setFallbackText("")
+ .build());
+ }
+
+ @Test
+ public void testRemoveSpaceGestureGesture() {
+ verifyEqualityAfterUnparcel(new RemoveSpaceGesture.Builder()
+ .setPoints(new PointF(1f, 2f), new PointF(3f, 4f))
+ .setFallbackText("")
+ .build());
+ }
+
+ @Test
+ public void testJoinOrSplitGestureGesture() {
+ verifyEqualityAfterUnparcel(new JoinOrSplitGesture.Builder()
+ .setJoinOrSplitPoint(new PointF(1f, 2f))
+ .setFallbackText("")
+ .build());
+ }
+
+ static void verifyEqualityAfterUnparcel(@NonNull HandwritingGesture gesture) {
+ assertEquals(gesture, cloneViaParcel(ParcelableHandwritingGesture.of(gesture)).get());
+ }
+
+ private static ParcelableHandwritingGesture cloneViaParcel(
+ @NonNull ParcelableHandwritingGesture original) {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return ParcelableHandwritingGesture.CREATOR.createFromParcel(parcel);
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index d4a663221cd7..95aa5d0de119 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -32,6 +32,7 @@ import android.app.Instrumentation;
import android.content.Context;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.view.HandwritingDelegateConfiguration;
import android.view.HandwritingInitiator;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -208,6 +209,30 @@ public class HandwritingInitiatorTest {
}
@Test
+ public void onTouchEvent_startHandwriting_delegate() {
+ int delegatorViewId = 234;
+ View delegatorView = new View(mContext);
+ delegatorView.setId(delegatorViewId);
+
+ mTestView.setHandwritingDelegateConfiguration(
+ new HandwritingDelegateConfiguration(
+ delegatorViewId,
+ () -> mHandwritingInitiator.onInputConnectionCreated(delegatorView)));
+
+ final int x1 = (sHwArea.left + sHwArea.right) / 2;
+ final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
+ MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+ mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+ final int x2 = x1 + mHandwritingSlop * 2;
+ final int y2 = y1;
+ MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+ mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+ verify(mHandwritingInitiator, times(1)).startHandwriting(delegatorView);
+ }
+
+ @Test
public void onTouchEvent_notStartHandwriting_whenHandwritingNotAvailable() {
final Rect rect = new Rect(600, 600, 900, 900);
final View testView = createView(rect, true /* autoHandwritingEnabled */,
diff --git a/core/tests/coretests/src/com/android/internal/security/OWNERS b/core/tests/coretests/src/com/android/internal/security/OWNERS
new file mode 100644
index 000000000000..4f4d8d7a0932
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/security/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 36824
+
+per-file VerityUtilsTest.java = file:platform/system/security:/fsverity/OWNERS
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 0f82c8fe904a..889edb3502c4 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -311,11 +311,11 @@ public final class PixelCopy {
/**
* Contains the result of a PixelCopy request
*/
- public static final class CopyResult {
+ public static final class Result {
private int mStatus;
private Bitmap mBitmap;
- private CopyResult(@CopyResultStatus int status, Bitmap bitmap) {
+ private Result(@CopyResultStatus int status, Bitmap bitmap) {
mStatus = status;
mBitmap = bitmap;
}
@@ -335,8 +335,8 @@ public final class PixelCopy {
/**
* If the PixelCopy {@link Request} was given a destination bitmap with
- * {@link Request#setDestinationBitmap(Bitmap)} then the returned bitmap will be the same
- * as the one given. If no destination bitmap was provided, then this
+ * {@link Request.Builder#setDestinationBitmap(Bitmap)} then the returned bitmap will be
+ * the same as the one given. If no destination bitmap was provided, then this
* will contain the automatically allocated Bitmap to hold the result.
*
* @return the Bitmap the copy request was stored in.
@@ -349,66 +349,199 @@ public final class PixelCopy {
}
/**
- * A builder to create the complete PixelCopy request, which is then executed by calling
- * {@link #request()}
+ * Represents a PixelCopy request.
+ *
+ * To create a copy request, use either of the PixelCopy.Request.ofWindow or
+ * PixelCopy.Request.ofSurface factories to create a {@link Request.Builder} for the
+ * given source content. After setting any optional parameters, such as
+ * {@link Builder#setSourceRect(Rect)}, build the request with {@link Builder#build()} and
+ * then execute it with {@link PixelCopy#request(Request)}
*/
public static final class Request {
+ private final Surface mSource;
+ private final Consumer<Result> mListener;
+ private final Executor mListenerThread;
+ private final Rect mSourceInsets;
+ private Rect mSrcRect;
+ private Bitmap mDest;
+
private Request(Surface source, Rect sourceInsets, Executor listenerThread,
- Consumer<CopyResult> listener) {
+ Consumer<Result> listener) {
this.mSource = source;
this.mSourceInsets = sourceInsets;
this.mListenerThread = listenerThread;
this.mListener = listener;
}
- private final Surface mSource;
- private final Consumer<CopyResult> mListener;
- private final Executor mListenerThread;
- private final Rect mSourceInsets;
- private Rect mSrcRect;
- private Bitmap mDest;
+ /**
+ * A builder to create the complete PixelCopy request, which is then executed by calling
+ * {@link #request(Request)} with the built request returned from {@link #build()}
+ */
+ public static final class Builder {
+ private Request mRequest;
+
+ private Builder(Request request) {
+ mRequest = request;
+ }
+
+ private void requireNotBuilt() {
+ if (mRequest == null) {
+ throw new IllegalStateException("build() already called on this builder");
+ }
+ }
+
+ /**
+ * Sets the region of the source to copy from. By default, the entire source is copied
+ * to the output. If only a subset of the source is necessary to be copied, specifying
+ * a srcRect will improve performance by reducing
+ * the amount of data being copied.
+ *
+ * @param srcRect The area of the source to read from. Null or empty will be treated to
+ * mean the entire source
+ * @return this
+ */
+ public @NonNull Builder setSourceRect(@Nullable Rect srcRect) {
+ requireNotBuilt();
+ mRequest.mSrcRect = srcRect;
+ return this;
+ }
+
+ /**
+ * Specifies the output bitmap in which to store the result. By default, a Bitmap of
+ * format {@link android.graphics.Bitmap.Config#ARGB_8888} with a width & height
+ * matching that of the {@link #setSourceRect(Rect) source area} will be created to
+ * place the result.
+ *
+ * @param destination The bitmap to store the result, or null to have a bitmap
+ * automatically created of the appropriate size. If not null, must
+ * not be {@link Bitmap#isRecycled() recycled} and must be
+ * {@link Bitmap#isMutable() mutable}.
+ * @return this
+ */
+ public @NonNull Builder setDestinationBitmap(@Nullable Bitmap destination) {
+ requireNotBuilt();
+ if (destination != null) {
+ validateBitmapDest(destination);
+ }
+ mRequest.mDest = destination;
+ return this;
+ }
+
+ /**
+ * @return The built {@link PixelCopy.Request}
+ */
+ public @NonNull Request build() {
+ requireNotBuilt();
+ Request ret = mRequest;
+ mRequest = null;
+ return ret;
+ }
+ }
/**
- * Sets the region of the source to copy from. By default, the entire source is copied to
- * the output. If only a subset of the source is necessary to be copied, specifying a
- * srcRect will improve performance by reducing
- * the amount of data being copied.
+ * Creates a PixelCopy request for the given {@link Window}
+ * @param source The Window to copy from
+ * @param callbackExecutor The executor to run the callback on
+ * @param listener The callback for when the copy request is completed
+ * @return A {@link Builder} builder to set the optional params & execute the request
+ */
+ public static @NonNull Builder ofWindow(@NonNull Window source,
+ @NonNull Executor callbackExecutor,
+ @NonNull Consumer<Result> listener) {
+ final Rect insets = new Rect();
+ final Surface surface = sourceForWindow(source, insets);
+ return new Builder(new Request(surface, insets, callbackExecutor, listener));
+ }
+
+ /**
+ * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
+ * attached to.
+ *
+ * Note that this copy request is not cropped to the area the View occupies by default. If
+ * that behavior is desired, use {@link View#getLocationInWindow(int[])} combined with
+ * {@link Builder#setSourceRect(Rect)} to set a crop area to restrict the copy operation.
*
- * @param srcRect The area of the source to read from. Null or empty will be treated to
- * mean the entire source
- * @return this
+ * @param source A View that {@link View#isAttachedToWindow() is attached} to a window that
+ * will be used to retrieve the window to copy from.
+ * @param callbackExecutor The executor to run the callback on
+ * @param listener The callback for when the copy request is completed
+ * @return A {@link Builder} builder to set the optional params & execute the request
*/
- public @NonNull Request setSourceRect(@Nullable Rect srcRect) {
- this.mSrcRect = srcRect;
- return this;
+ public static @NonNull Builder ofWindow(@NonNull View source,
+ @NonNull Executor callbackExecutor,
+ @NonNull Consumer<Result> listener) {
+ if (source == null || !source.isAttachedToWindow()) {
+ throw new IllegalArgumentException(
+ "View must not be null & must be attached to window");
+ }
+ final Rect insets = new Rect();
+ Surface surface = null;
+ final ViewRootImpl root = source.getViewRootImpl();
+ if (root != null) {
+ surface = root.mSurface;
+ insets.set(root.mWindowAttributes.surfaceInsets);
+ }
+ if (surface == null || !surface.isValid()) {
+ throw new IllegalArgumentException(
+ "Window doesn't have a backing surface!");
+ }
+ return new Builder(new Request(surface, insets, callbackExecutor, listener));
}
/**
- * Specifies the output bitmap in which to store the result. By default, a Bitmap of format
- * {@link android.graphics.Bitmap.Config#ARGB_8888} with a width & height matching that
- * of the {@link #setSourceRect(Rect) source area} will be created to place the result.
+ * Creates a PixelCopy request for the given {@link Surface}
*
- * @param destination The bitmap to store the result, or null to have a bitmap
- * automatically created of the appropriate size. If not null, must not
- * be {@link Bitmap#isRecycled() recycled} and must be
- * {@link Bitmap#isMutable() mutable}.
- * @return this
+ * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
+ * @param callbackExecutor The executor to run the callback on
+ * @param listener The callback for when the copy request is completed
+ * @return A {@link Builder} builder to set the optional params & execute the request
*/
- public @NonNull Request setDestinationBitmap(@Nullable Bitmap destination) {
- if (destination != null) {
- validateBitmapDest(destination);
+ public static @NonNull Builder ofSurface(@NonNull Surface source,
+ @NonNull Executor callbackExecutor,
+ @NonNull Consumer<Result> listener) {
+ if (source == null || !source.isValid()) {
+ throw new IllegalArgumentException("Source must not be null & must be valid");
}
- this.mDest = destination;
- return this;
+ return new Builder(new Request(source, null, callbackExecutor, listener));
+ }
+
+ /**
+ * Creates a PixelCopy request for the {@link Surface} belonging to the
+ * given {@link SurfaceView}
+ *
+ * @param source The SurfaceView to copy from. The backing surface must be
+ * {@link Surface#isValid() valid}
+ * @param callbackExecutor The executor to run the callback on
+ * @param listener The callback for when the copy request is completed
+ * @return A {@link Builder} builder to set the optional params & execute the request
+ */
+ public static @NonNull Builder ofSurface(@NonNull SurfaceView source,
+ @NonNull Executor callbackExecutor,
+ @NonNull Consumer<Result> listener) {
+ return ofSurface(source.getHolder().getSurface(), callbackExecutor, listener);
+ }
+
+ /**
+ * @return The destination bitmap as set by {@link Builder#setDestinationBitmap(Bitmap)}
+ */
+ public @Nullable Bitmap getDestinationBitmap() {
+ return mDest;
}
/**
- * Executes the request.
+ * @return The source rect to copy from as set by {@link Builder#setSourceRect(Rect)}
+ */
+ public @Nullable Rect getSourceRect() {
+ return mSrcRect;
+ }
+
+ /**
+ * @hide
*/
public void request() {
if (!mSource.isValid()) {
mListenerThread.execute(() -> mListener.accept(
- new CopyResult(ERROR_SOURCE_INVALID, null)));
+ new Result(ERROR_SOURCE_INVALID, null)));
return;
}
HardwareRenderer.copySurfaceInto(mSource, new HardwareRenderer.CopyRequest(
@@ -416,93 +549,18 @@ public final class PixelCopy {
@Override
public void onCopyFinished(int result) {
mListenerThread.execute(() -> mListener.accept(
- new CopyResult(result, mDestinationBitmap)));
+ new Result(result, mDestinationBitmap)));
}
});
}
}
/**
- * Creates a PixelCopy request for the given {@link Window}
- * @param source The Window to copy from
- * @param callbackExecutor The executor to run the callback on
- * @param listener The callback for when the copy request is completed
- * @return A {@link Request} builder to set the optional params & execute the request
- */
- public static @NonNull Request ofWindow(@NonNull Window source,
- @NonNull Executor callbackExecutor,
- @NonNull Consumer<CopyResult> listener) {
- final Rect insets = new Rect();
- final Surface surface = sourceForWindow(source, insets);
- return new Request(surface, insets, callbackExecutor, listener);
- }
-
- /**
- * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
- * attached to.
- *
- * Note that this copy request is not cropped to the area the View occupies by default. If that
- * behavior is desired, use {@link View#getLocationInWindow(int[])} combined with
- * {@link Request#setSourceRect(Rect)} to set a crop area to restrict the copy operation.
- *
- * @param source A View that {@link View#isAttachedToWindow() is attached} to a window that
- * will be used to retrieve the window to copy from.
- * @param callbackExecutor The executor to run the callback on
- * @param listener The callback for when the copy request is completed
- * @return A {@link Request} builder to set the optional params & execute the request
- */
- public static @NonNull Request ofWindow(@NonNull View source,
- @NonNull Executor callbackExecutor,
- @NonNull Consumer<CopyResult> listener) {
- if (source == null || !source.isAttachedToWindow()) {
- throw new IllegalArgumentException(
- "View must not be null & must be attached to window");
- }
- final Rect insets = new Rect();
- Surface surface = null;
- final ViewRootImpl root = source.getViewRootImpl();
- if (root != null) {
- surface = root.mSurface;
- insets.set(root.mWindowAttributes.surfaceInsets);
- }
- if (surface == null || !surface.isValid()) {
- throw new IllegalArgumentException(
- "Window doesn't have a backing surface!");
- }
- return new Request(surface, insets, callbackExecutor, listener);
- }
-
- /**
- * Creates a PixelCopy request for the given {@link Surface}
- *
- * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
- * @param callbackExecutor The executor to run the callback on
- * @param listener The callback for when the copy request is completed
- * @return A {@link Request} builder to set the optional params & execute the request
- */
- public static @NonNull Request ofSurface(@NonNull Surface source,
- @NonNull Executor callbackExecutor,
- @NonNull Consumer<CopyResult> listener) {
- if (source == null || !source.isValid()) {
- throw new IllegalArgumentException("Source must not be null & must be valid");
- }
- return new Request(source, null, callbackExecutor, listener);
- }
-
- /**
- * Creates a PixelCopy request for the {@link Surface} belonging to the
- * given {@link SurfaceView}
- *
- * @param source The SurfaceView to copy from. The backing surface must be
- * {@link Surface#isValid() valid}
- * @param callbackExecutor The executor to run the callback on
- * @param listener The callback for when the copy request is completed
- * @return A {@link Request} builder to set the optional params & execute the request
+ * Executes the pixel copy request
+ * @param request The request to execute
*/
- public static @NonNull Request ofSurface(@NonNull SurfaceView source,
- @NonNull Executor callbackExecutor,
- @NonNull Consumer<CopyResult> listener) {
- return ofSurface(source.getHolder().getSurface(), callbackExecutor, listener);
+ public static void request(@NonNull Request request) {
+ request.request();
}
private PixelCopy() {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 2b36b4c0307d..85bad174194c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -335,6 +335,7 @@ public class PipTransition extends PipTransitionController {
final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo();
if (taskInfo != null) {
startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(),
+ mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
new Rect(mExitDestinationBounds), Surface.ROTATION_0);
}
mExitDestinationBounds.setEmpty();
@@ -475,6 +476,20 @@ public class PipTransition extends PipTransitionController {
taskInfo);
return;
}
+
+ // When exiting PiP, the PiP leash may be an Activity of a multi-windowing Task, for which
+ // case it may not be in the screen coordinate.
+ // Reparent the pip leash to the root with max layer so that we can animate it outside of
+ // parent crop, and make sure it is not covered by other windows.
+ final SurfaceControl pipLeash = pipChange.getLeash();
+ startTransaction.reparent(pipLeash, info.getRootLeash());
+ startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
+ // Note: because of this, the bounds to animate should be translated to the root coordinate.
+ final Point offset = info.getRootOffset();
+ final Rect currentBounds = mPipBoundsState.getBounds();
+ currentBounds.offset(-offset.x, -offset.y);
+ startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
+
mFinishCallback = (wct, wctCB) -> {
mPipOrganizer.onExitPipFinished(taskInfo);
finishCallback.onTransitionFinished(wct, wctCB);
@@ -496,18 +511,17 @@ public class PipTransition extends PipTransitionController {
if (displayRotationChange != null) {
// Exiting PIP to fullscreen with orientation change.
startExpandAndRotationAnimation(info, startTransaction, finishTransaction,
- displayRotationChange, taskInfo, pipChange);
+ displayRotationChange, taskInfo, pipChange, offset);
return;
}
}
// Set the initial frame as scaling the end to the start.
final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds());
- final Point offset = pipChange.getEndRelOffset();
destinationBounds.offset(-offset.x, -offset.y);
- startTransaction.setWindowCrop(pipChange.getLeash(), destinationBounds);
- mSurfaceTransactionHelper.scale(startTransaction, pipChange.getLeash(),
- destinationBounds, mPipBoundsState.getBounds());
+ startTransaction.setWindowCrop(pipLeash, destinationBounds);
+ mSurfaceTransactionHelper.scale(startTransaction, pipLeash, destinationBounds,
+ currentBounds);
startTransaction.apply();
// Check if it is fixed rotation.
@@ -532,19 +546,21 @@ public class PipTransition extends PipTransitionController {
y = destinationBounds.bottom;
}
mSurfaceTransactionHelper.rotateAndScaleWithCrop(finishTransaction,
- pipChange.getLeash(), endBounds, endBounds, new Rect(), degree, x, y,
+ pipLeash, endBounds, endBounds, new Rect(), degree, x, y,
true /* isExpanding */, rotationDelta == ROTATION_270 /* clockwise */);
} else {
rotationDelta = Surface.ROTATION_0;
}
- startExpandAnimation(taskInfo, pipChange.getLeash(), destinationBounds, rotationDelta);
+ startExpandAnimation(taskInfo, pipLeash, currentBounds, currentBounds, destinationBounds,
+ rotationDelta);
}
private void startExpandAndRotationAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionInfo.Change displayRotationChange,
- @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange) {
+ @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange,
+ @NonNull Point offset) {
final int rotateDelta = deltaRotation(displayRotationChange.getStartRotation(),
displayRotationChange.getEndRotation());
@@ -556,7 +572,6 @@ public class PipTransition extends PipTransitionController {
final Rect startBounds = new Rect(pipChange.getStartAbsBounds());
rotateBounds(startBounds, displayRotationChange.getStartAbsBounds(), rotateDelta);
final Rect endBounds = new Rect(pipChange.getEndAbsBounds());
- final Point offset = pipChange.getEndRelOffset();
startBounds.offset(-offset.x, -offset.y);
endBounds.offset(-offset.x, -offset.y);
@@ -592,11 +607,12 @@ public class PipTransition extends PipTransitionController {
}
private void startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
- final Rect destinationBounds, final int rotationDelta) {
+ final Rect baseBounds, final Rect startBounds, final Rect endBounds,
+ final int rotationDelta) {
final PipAnimationController.PipTransitionAnimator animator =
- mPipAnimationController.getAnimator(taskInfo, leash, mPipBoundsState.getBounds(),
- mPipBoundsState.getBounds(), destinationBounds, null,
- TRANSITION_DIRECTION_LEAVE_PIP, 0 /* startingAngle */, rotationDelta);
+ mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds,
+ endBounds, null /* sourceHintRect */, TRANSITION_DIRECTION_LEAVE_PIP,
+ 0 /* startingAngle */, rotationDelta);
animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index af79386caf9c..928e71f8d3a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -46,6 +46,7 @@ import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -319,6 +320,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final int wallpaperTransit = getWallpaperTransitType(info);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.hasAllFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY
+ | FLAG_IS_BEHIND_STARTING_WINDOW)) {
+ // Don't animate embedded activity if it is covered by the starting window.
+ // Non-embedded case still needs animation because the container can still animate
+ // the starting window together, e.g. CLOSE or CHANGE type.
+ continue;
+ }
+ if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) {
+ // Wallpaper, IME, and system windows don't need any default animations.
+ continue;
+ }
final boolean isTask = change.getTaskInfo() != null;
boolean isSeamlessDisplayChange = false;
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index 73159c981b82..ad7a531b589d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -145,15 +145,19 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
// robust enough to get the correct end state.
}
+ @Presubmit
@Test
fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ @Presubmit
@Test
fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+ @Presubmit
@Test
fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
+ @Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp,
@@ -161,6 +165,7 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
portraitPosTop = true
)
+ @Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp,
@@ -168,9 +173,11 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
portraitPosTop = false
)
+ @Presubmit
@Test
fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ @Presubmit
@Test
fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 55883ab2ef70..d01f3d310fc3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -39,6 +39,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -110,6 +111,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
}
@Test
+ @UiThreadTest
public void instantiateController_registerDumpCallback() {
doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
@@ -118,6 +120,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
}
@Test
+ @UiThreadTest
public void instantiateController_registerCommandCallback() {
doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
@@ -126,6 +129,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
}
@Test
+ @UiThreadTest
public void testControllerRegistersKeyguardChangeListener() {
doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
@@ -134,6 +138,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
}
@Test
+ @UiThreadTest
public void instantiateController_addExternalInterface() {
doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 835087007b30..3569860b6128 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -50,6 +50,7 @@ import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -113,6 +114,7 @@ public class StageCoordinatorTests extends ShellTestCase {
private StageCoordinator mStageCoordinator;
@Before
+ @UiThreadTest
public void setup() {
MockitoAnnotations.initMocks(this);
mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 26cb9f8e9ee1..b4b908dbff16 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -104,8 +104,8 @@ public final class MediaRouter2 {
private final String mPackageName;
/**
- * Stores the latest copy of all routes received from {@link MediaRouter2ServiceImpl}, without
- * any filtering, sorting, or deduplication.
+ * Stores the latest copy of all routes received from the system server, without any filtering,
+ * sorting, or deduplication.
*
* <p>Uses {@link MediaRoute2Info#getId()} to set each entry's key.
*/
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 56fb1a91aa90..0988cba2f424 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -36,7 +36,7 @@ import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
-import com.android.credentialmanager.jetpack.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
+import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
// Consider repo per screen, similar to view model?
class CredentialManagerRepo(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index 8e30208e75d9..aeea46a85caf 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -39,8 +39,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.R
-import com.android.credentialmanager.jetpack.CredentialEntryUi.Companion.TYPE_PASSWORD_CREDENTIAL
-import com.android.credentialmanager.jetpack.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
+import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PASSWORD_CREDENTIAL
+import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
import com.android.credentialmanager.ui.theme.Grey100
import com.android.credentialmanager.ui.theme.Shapes
import com.android.credentialmanager.ui.theme.Typography
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
new file mode 100644
index 000000000000..7e7dbde8655a
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.credentialmanager.jetpack.developer
+
+import android.credentials.Credential
+import android.os.Bundle
+
+/**
+ * Base request class for registering a credential.
+ *
+ * @property type the credential type
+ * @property data the request data in the [Bundle] format
+ * @property requireSystemProvider true if must only be fulfilled by a system provider and false
+ * otherwise
+ */
+open class CreateCredentialRequest(
+ val type: String,
+ val data: Bundle,
+ val requireSystemProvider: Boolean,
+) {
+ companion object {
+ @JvmStatic
+ fun createFrom(from: android.credentials.CreateCredentialRequest): CreateCredentialRequest {
+ return try {
+ when (from.type) {
+ Credential.TYPE_PASSWORD_CREDENTIAL ->
+ CreatePasswordRequest.createFrom(from.data)
+ PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
+ CreatePublicKeyCredentialBaseRequest.createFrom(from.data)
+ else ->
+ CreateCredentialRequest(from.type, from.data, from.requireSystemProvider())
+ }
+ } catch (e: FrameworkClassParsingException) {
+ CreateCredentialRequest(from.type, from.data, from.requireSystemProvider())
+ }
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt
new file mode 100644
index 000000000000..f0da9f9d1866
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.credentialmanager.jetpack.developer
+
+import android.credentials.Credential
+import android.os.Bundle
+
+/**
+ * A request to save the user password credential with their password provider.
+ *
+ * @property id the user id associated with the password
+ * @property password the password
+ * @throws NullPointerException If [id] is null
+ * @throws NullPointerException If [password] is null
+ * @throws IllegalArgumentException If [password] is empty
+ */
+class CreatePasswordRequest constructor(
+ val id: String,
+ val password: String,
+) : CreateCredentialRequest(
+ Credential.TYPE_PASSWORD_CREDENTIAL,
+ toBundle(id, password),
+ false,
+) {
+
+ init {
+ require(password.isNotEmpty()) { "password should not be empty" }
+ }
+
+ companion object {
+ const val BUNDLE_KEY_ID = "androidx.credentials.BUNDLE_KEY_ID"
+ const val BUNDLE_KEY_PASSWORD = "androidx.credentials.BUNDLE_KEY_PASSWORD"
+
+ @JvmStatic
+ internal fun toBundle(id: String, password: String): Bundle {
+ val bundle = Bundle()
+ bundle.putString(BUNDLE_KEY_ID, id)
+ bundle.putString(BUNDLE_KEY_PASSWORD, password)
+ return bundle
+ }
+
+ @JvmStatic
+ fun createFrom(data: Bundle): CreatePasswordRequest {
+ try {
+ val id = data.getString(BUNDLE_KEY_ID)
+ val password = data.getString(BUNDLE_KEY_PASSWORD)
+ return CreatePasswordRequest(id!!, password!!)
+ } catch (e: Exception) {
+ throw FrameworkClassParsingException()
+ }
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
new file mode 100644
index 000000000000..26d61f9eb7a9
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * Base request class for registering a public key credential.
+ *
+ * @property requestJson The request in JSON format
+ * @throws NullPointerException If [requestJson] is null. This is handled by the Kotlin runtime
+ * @throws IllegalArgumentException If [requestJson] is empty
+ *
+ * @hide
+ */
+abstract class CreatePublicKeyCredentialBaseRequest constructor(
+ val requestJson: String,
+ type: String,
+ data: Bundle,
+ requireSystemProvider: Boolean,
+) : CreateCredentialRequest(type, data, requireSystemProvider) {
+
+ init {
+ require(requestJson.isNotEmpty()) { "request json must not be empty" }
+ }
+
+ companion object {
+ const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
+ const val BUNDLE_KEY_SUBTYPE = "androidx.credentials.BUNDLE_KEY_SUBTYPE"
+
+ @JvmStatic
+ fun createFrom(data: Bundle): CreatePublicKeyCredentialBaseRequest {
+ return when (data.getString(BUNDLE_KEY_SUBTYPE)) {
+ CreatePublicKeyCredentialRequest
+ .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST ->
+ CreatePublicKeyCredentialRequestPrivileged.createFrom(data)
+ CreatePublicKeyCredentialRequestPrivileged
+ .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED ->
+ CreatePublicKeyCredentialRequestPrivileged.createFrom(data)
+ else -> throw FrameworkClassParsingException()
+ }
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt
new file mode 100644
index 000000000000..2eda90b827dc
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * A request to register a passkey from the user's public key credential provider.
+ *
+ * @property requestJson the request in JSON format
+ * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
+ * true by default
+ * @throws NullPointerException If [requestJson] or [allowHybrid] is null. This is handled by the
+ * Kotlin runtime
+ * @throws IllegalArgumentException If [requestJson] is empty
+ *
+ * @hide
+ */
+class CreatePublicKeyCredentialRequest @JvmOverloads constructor(
+ requestJson: String,
+ @get:JvmName("allowHybrid")
+ val allowHybrid: Boolean = true
+) : CreatePublicKeyCredentialBaseRequest(
+ requestJson,
+ PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
+ toBundle(requestJson, allowHybrid),
+ false,
+) {
+ companion object {
+ const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
+ const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST =
+ "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST"
+
+ @JvmStatic
+ internal fun toBundle(requestJson: String, allowHybrid: Boolean): Bundle {
+ val bundle = Bundle()
+ bundle.putString(BUNDLE_KEY_SUBTYPE,
+ BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST)
+ bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
+ bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+ return bundle
+ }
+
+ @JvmStatic
+ fun createFrom(data: Bundle): CreatePublicKeyCredentialRequest {
+ try {
+ val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
+ val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
+ return CreatePublicKeyCredentialRequest(requestJson!!, (allowHybrid!!) as Boolean)
+ } catch (e: Exception) {
+ throw FrameworkClassParsingException()
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt
new file mode 100644
index 000000000000..36324f83a7e5
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt
@@ -0,0 +1,143 @@
+/*
+ * 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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * A privileged request to register a passkey from the user’s public key credential provider, where
+ * the caller can modify the rp. Only callers with privileged permission, e.g. user’s default
+ * brower, caBLE, can use this.
+ *
+ * @property requestJson the privileged request in JSON format
+ * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
+ * true by default
+ * @property rp the expected true RP ID which will override the one in the [requestJson]
+ * @property clientDataHash a hash that is used to verify the [rp] Identity
+ * @throws NullPointerException If any of [allowHybrid], [requestJson], [rp], or [clientDataHash] is
+ * null. This is handled by the Kotlin runtime
+ * @throws IllegalArgumentException If any of [requestJson], [rp], or [clientDataHash] is empty
+ *
+ * @hide
+ */
+class CreatePublicKeyCredentialRequestPrivileged @JvmOverloads constructor(
+ requestJson: String,
+ val rp: String,
+ val clientDataHash: String,
+ @get:JvmName("allowHybrid")
+ val allowHybrid: Boolean = true
+) : CreatePublicKeyCredentialBaseRequest(
+ requestJson,
+ PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
+ toBundle(requestJson, rp, clientDataHash, allowHybrid),
+ false,
+) {
+
+ init {
+ require(rp.isNotEmpty()) { "rp must not be empty" }
+ require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" }
+ }
+
+ /** A builder for [CreatePublicKeyCredentialRequestPrivileged]. */
+ class Builder(var requestJson: String, var rp: String, var clientDataHash: String) {
+
+ private var allowHybrid: Boolean = true
+
+ /**
+ * Sets the privileged request in JSON format.
+ */
+ fun setRequestJson(requestJson: String): Builder {
+ this.requestJson = requestJson
+ return this
+ }
+
+ /**
+ * Sets whether hybrid credentials are allowed to fulfill this request, true by default.
+ */
+ fun setAllowHybrid(allowHybrid: Boolean): Builder {
+ this.allowHybrid = allowHybrid
+ return this
+ }
+
+ /**
+ * Sets the expected true RP ID which will override the one in the [requestJson].
+ */
+ fun setRp(rp: String): Builder {
+ this.rp = rp
+ return this
+ }
+
+ /**
+ * Sets a hash that is used to verify the [rp] Identity.
+ */
+ fun setClientDataHash(clientDataHash: String): Builder {
+ this.clientDataHash = clientDataHash
+ return this
+ }
+
+ /** Builds a [CreatePublicKeyCredentialRequestPrivileged]. */
+ fun build(): CreatePublicKeyCredentialRequestPrivileged {
+ return CreatePublicKeyCredentialRequestPrivileged(this.requestJson,
+ this.rp, this.clientDataHash, this.allowHybrid)
+ }
+ }
+
+ companion object {
+ const val BUNDLE_KEY_RP = "androidx.credentials.BUNDLE_KEY_RP"
+ const val BUNDLE_KEY_CLIENT_DATA_HASH =
+ "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH"
+ const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
+ const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED =
+ "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_" +
+ "PRIVILEGED"
+
+ @JvmStatic
+ internal fun toBundle(
+ requestJson: String,
+ rp: String,
+ clientDataHash: String,
+ allowHybrid: Boolean
+ ): Bundle {
+ val bundle = Bundle()
+ bundle.putString(BUNDLE_KEY_SUBTYPE,
+ BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED)
+ bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
+ bundle.putString(BUNDLE_KEY_RP, rp)
+ bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash)
+ bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+ return bundle
+ }
+
+ @JvmStatic
+ fun createFrom(data: Bundle): CreatePublicKeyCredentialRequestPrivileged {
+ try {
+ val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
+ val rp = data.getString(BUNDLE_KEY_RP)
+ val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH)
+ val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
+ return CreatePublicKeyCredentialRequestPrivileged(
+ requestJson!!,
+ rp!!,
+ clientDataHash!!,
+ (allowHybrid!!) as Boolean,
+ )
+ } catch (e: Exception) {
+ throw FrameworkClassParsingException()
+ }
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt
new file mode 100644
index 000000000000..ee08e9e30649
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * Base class for a credential with which the user consented to authenticate to the app.
+ *
+ * @property type the credential type
+ * @property data the credential data in the [Bundle] format.
+ */
+open class Credential(val type: String, val data: Bundle)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt
new file mode 100644
index 000000000000..497c272750ac
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.credentialmanager.jetpack.developer
+
+/**
+ * Internal exception used to indicate a parsing error while converting from a framework type to
+ * a jetpack type.
+ *
+ * @hide
+ */
+internal class FrameworkClassParsingException : Exception() \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
new file mode 100644
index 000000000000..eb65241ed4df
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.credentialmanager.jetpack.developer
+
+import android.credentials.Credential
+import android.os.Bundle
+
+/**
+ * Base request class for getting a registered credential.
+ *
+ * @property type the credential type
+ * @property data the request data in the [Bundle] format
+ * @property requireSystemProvider true if must only be fulfilled by a system provider and false
+ * otherwise
+ */
+open class GetCredentialOption(
+ val type: String,
+ val data: Bundle,
+ val requireSystemProvider: Boolean,
+) {
+ companion object {
+ @JvmStatic
+ fun createFrom(from: android.credentials.GetCredentialOption): GetCredentialOption {
+ return try {
+ when (from.type) {
+ Credential.TYPE_PASSWORD_CREDENTIAL ->
+ GetPasswordOption.createFrom(from.data)
+ PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
+ GetPublicKeyCredentialBaseOption.createFrom(from.data)
+ else ->
+ GetCredentialOption(from.type, from.data, from.requireSystemProvider())
+ }
+ } catch (e: FrameworkClassParsingException) {
+ GetCredentialOption(from.type, from.data, from.requireSystemProvider())
+ }
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
new file mode 100644
index 000000000000..7f9256ed6c75
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.credentialmanager.jetpack.developer
+
+/**
+ * Encapsulates a request to get a user credential.
+ *
+ * @property getCredentialOptions the list of [GetCredentialOption] from which the user can choose
+ * one to authenticate to the app
+ * @throws IllegalArgumentException If [getCredentialOptions] is empty
+ */
+class GetCredentialRequest constructor(
+ val getCredentialOptions: List<GetCredentialOption>,
+) {
+
+ init {
+ require(getCredentialOptions.isNotEmpty()) { "credentialRequests should not be empty" }
+ }
+
+ /** A builder for [GetCredentialRequest]. */
+ class Builder {
+ private var getCredentialOptions: MutableList<GetCredentialOption> = mutableListOf()
+
+ /** Adds a specific type of [GetCredentialOption]. */
+ fun addGetCredentialOption(getCredentialOption: GetCredentialOption): Builder {
+ getCredentialOptions.add(getCredentialOption)
+ return this
+ }
+
+ /** Sets the list of [GetCredentialOption]. */
+ fun setGetCredentialOptions(getCredentialOptions: List<GetCredentialOption>): Builder {
+ this.getCredentialOptions = getCredentialOptions.toMutableList()
+ return this
+ }
+
+ /**
+ * Builds a [GetCredentialRequest].
+ *
+ * @throws IllegalArgumentException If [getCredentialOptions] is empty
+ */
+ fun build(): GetCredentialRequest {
+ return GetCredentialRequest(getCredentialOptions.toList())
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun createFrom(from: android.credentials.GetCredentialRequest): GetCredentialRequest {
+ return GetCredentialRequest(
+ from.getCredentialOptions.map {GetCredentialOption.createFrom(it)}
+ )
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt
new file mode 100644
index 000000000000..2facad17b04e
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.credentialmanager.jetpack.developer
+
+import android.credentials.Credential
+import android.os.Bundle
+
+/** A request to retrieve the user's saved application password from their password provider. */
+class GetPasswordOption : GetCredentialOption(
+ Credential.TYPE_PASSWORD_CREDENTIAL,
+ Bundle(),
+ false,
+) {
+ companion object {
+ @JvmStatic
+ fun createFrom(data: Bundle): GetPasswordOption {
+ return GetPasswordOption()
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt
new file mode 100644
index 000000000000..9b51b306dd6b
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * Base request class for getting a registered public key credential.
+ *
+ * @property requestJson the request in JSON format
+ * @throws NullPointerException If [requestJson] is null - auto handled by the
+ * Kotlin runtime
+ * @throws IllegalArgumentException If [requestJson] is empty
+ *
+ * @hide
+ */
+abstract class GetPublicKeyCredentialBaseOption constructor(
+ val requestJson: String,
+ type: String,
+ data: Bundle,
+ requireSystemProvider: Boolean,
+) : GetCredentialOption(type, data, requireSystemProvider) {
+
+ init {
+ require(requestJson.isNotEmpty()) { "request json must not be empty" }
+ }
+
+ companion object {
+ const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
+ const val BUNDLE_KEY_SUBTYPE = "androidx.credentials.BUNDLE_KEY_SUBTYPE"
+
+ @JvmStatic
+ fun createFrom(data: Bundle): GetPublicKeyCredentialBaseOption {
+ return when (data.getString(BUNDLE_KEY_SUBTYPE)) {
+ GetPublicKeyCredentialOption
+ .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION ->
+ GetPublicKeyCredentialOption.createFrom(data)
+ GetPublicKeyCredentialOptionPrivileged
+ .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED ->
+ GetPublicKeyCredentialOptionPrivileged.createFrom(data)
+ else -> throw FrameworkClassParsingException()
+ }
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt
new file mode 100644
index 000000000000..6f13c17f9b6e
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * A request to get passkeys from the user's public key credential provider.
+ *
+ * @property requestJson the request in JSON format
+ * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
+ * true by default
+ * @throws NullPointerException If [requestJson] or [allowHybrid] is null. It is handled by the
+ * Kotlin runtime
+ * @throws IllegalArgumentException If [requestJson] is empty
+ *
+ * @hide
+ */
+class GetPublicKeyCredentialOption @JvmOverloads constructor(
+ requestJson: String,
+ @get:JvmName("allowHybrid")
+ val allowHybrid: Boolean = true,
+) : GetPublicKeyCredentialBaseOption(
+ requestJson,
+ PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
+ toBundle(requestJson, allowHybrid),
+ false
+) {
+ companion object {
+ const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
+ const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION =
+ "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION"
+
+ @JvmStatic
+ internal fun toBundle(requestJson: String, allowHybrid: Boolean): Bundle {
+ val bundle = Bundle()
+ bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
+ bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+ return bundle
+ }
+
+ @JvmStatic
+ fun createFrom(data: Bundle): GetPublicKeyCredentialOption {
+ try {
+ val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
+ val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
+ return GetPublicKeyCredentialOption(requestJson!!, (allowHybrid!!) as Boolean)
+ } catch (e: Exception) {
+ throw FrameworkClassParsingException()
+ }
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt
new file mode 100644
index 000000000000..79c62a1cdfbe
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * A privileged request to get passkeys from the user's public key credential provider. The caller
+ * can modify the RP. Only callers with privileged permission (e.g. user's public browser or caBLE)
+ * can use this.
+ *
+ * @property requestJson the privileged request in JSON format
+ * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
+ * true by default
+ * @property rp the expected true RP ID which will override the one in the [requestJson]
+ * @property clientDataHash a hash that is used to verify the [rp] Identity
+ * @throws NullPointerException If any of [allowHybrid], [requestJson], [rp], or [clientDataHash]
+ * is null. This is handled by the Kotlin runtime
+ * @throws IllegalArgumentException If any of [requestJson], [rp], or [clientDataHash] is empty
+ *
+ * @hide
+ */
+class GetPublicKeyCredentialOptionPrivileged @JvmOverloads constructor(
+ requestJson: String,
+ val rp: String,
+ val clientDataHash: String,
+ @get:JvmName("allowHybrid")
+ val allowHybrid: Boolean = true
+) : GetPublicKeyCredentialBaseOption(
+ requestJson,
+ PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
+ toBundle(requestJson, rp, clientDataHash, allowHybrid),
+ false,
+) {
+
+ init {
+ require(rp.isNotEmpty()) { "rp must not be empty" }
+ require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" }
+ }
+
+ /** A builder for [GetPublicKeyCredentialOptionPrivileged]. */
+ class Builder(var requestJson: String, var rp: String, var clientDataHash: String) {
+
+ private var allowHybrid: Boolean = true
+
+ /**
+ * Sets the privileged request in JSON format.
+ */
+ fun setRequestJson(requestJson: String): Builder {
+ this.requestJson = requestJson
+ return this
+ }
+
+ /**
+ * Sets whether hybrid credentials are allowed to fulfill this request, true by default.
+ */
+ fun setAllowHybrid(allowHybrid: Boolean): Builder {
+ this.allowHybrid = allowHybrid
+ return this
+ }
+
+ /**
+ * Sets the expected true RP ID which will override the one in the [requestJson].
+ */
+ fun setRp(rp: String): Builder {
+ this.rp = rp
+ return this
+ }
+
+ /**
+ * Sets a hash that is used to verify the [rp] Identity.
+ */
+ fun setClientDataHash(clientDataHash: String): Builder {
+ this.clientDataHash = clientDataHash
+ return this
+ }
+
+ /** Builds a [GetPublicKeyCredentialOptionPrivileged]. */
+ fun build(): GetPublicKeyCredentialOptionPrivileged {
+ return GetPublicKeyCredentialOptionPrivileged(this.requestJson,
+ this.rp, this.clientDataHash, this.allowHybrid)
+ }
+ }
+
+ companion object {
+ const val BUNDLE_KEY_RP = "androidx.credentials.BUNDLE_KEY_RP"
+ const val BUNDLE_KEY_CLIENT_DATA_HASH =
+ "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH"
+ const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
+ const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED =
+ "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION" +
+ "_PRIVILEGED"
+
+ @JvmStatic
+ internal fun toBundle(
+ requestJson: String,
+ rp: String,
+ clientDataHash: String,
+ allowHybrid: Boolean
+ ): Bundle {
+ val bundle = Bundle()
+ bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
+ bundle.putString(BUNDLE_KEY_RP, rp)
+ bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash)
+ bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+ return bundle
+ }
+
+ @JvmStatic
+ fun createFrom(data: Bundle): GetPublicKeyCredentialOptionPrivileged {
+ try {
+ val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
+ val rp = data.getString(BUNDLE_KEY_RP)
+ val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH)
+ val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
+ return GetPublicKeyCredentialOptionPrivileged(
+ requestJson!!,
+ rp!!,
+ clientDataHash!!,
+ (allowHybrid!!) as Boolean,
+ )
+ } catch (e: Exception) {
+ throw FrameworkClassParsingException()
+ }
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt
new file mode 100644
index 000000000000..b45a63bcf4ec
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * Represents the user's passkey credential granted by the user for app sign-in.
+ *
+ * @property authenticationResponseJson the public key credential authentication response in
+ * JSON format that follows the standard webauthn json format shown at
+ * [this w3c link](https://w3c.github.io/webauthn/#dictdef-authenticationresponsejson)
+ * @throws NullPointerException If [authenticationResponseJson] is null. This is handled by the
+ * kotlin runtime
+ * @throws IllegalArgumentException If [authenticationResponseJson] is empty
+ *
+ * @hide
+ */
+class PublicKeyCredential constructor(
+ val authenticationResponseJson: String
+) : Credential(
+ TYPE_PUBLIC_KEY_CREDENTIAL,
+ toBundle(authenticationResponseJson)
+) {
+
+ init {
+ require(authenticationResponseJson.isNotEmpty()) {
+ "authentication response JSON must not be empty" }
+ }
+ companion object {
+ /** The type value for public key credential related operations. */
+ const val TYPE_PUBLIC_KEY_CREDENTIAL: String =
+ "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"
+ const val BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON =
+ "androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON"
+
+ @JvmStatic
+ internal fun toBundle(authenticationResponseJson: String): Bundle {
+ val bundle = Bundle()
+ bundle.putString(BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON, authenticationResponseJson)
+ return bundle
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt
index d4341b498fe0..1e639fe6bd55 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.credentialmanager.jetpack
+package com.android.credentialmanager.jetpack.provider
import android.app.slice.Slice
import android.credentials.ui.Entry
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
index d6f1b5f5c8e9..12ab436e1507 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.credentialmanager.jetpack
+package com.android.credentialmanager.jetpack.provider
import android.app.slice.Slice
import android.graphics.drawable.Icon
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasskeyCredentialEntryUi.kt
index bb3b206500b4..c5dbe66e8dbb 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasskeyCredentialEntryUi.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.credentialmanager.jetpack
+package com.android.credentialmanager.jetpack.provider
import android.app.slice.Slice
import android.credentials.ui.Entry
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasswordCredentialEntryUi.kt
index 7311b7081343..5049503b32c1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasswordCredentialEntryUi.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.credentialmanager.jetpack
+package com.android.credentialmanager.jetpack.provider
import android.app.slice.Slice
import android.credentials.ui.Entry
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
index fad3309fb86f..b260cf63587c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.credentialmanager.jetpack
+package com.android.credentialmanager.jetpack.provider
import android.app.slice.Slice
import android.credentials.ui.Entry
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index c659525d42a5..f170ead70b77 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -54,6 +54,7 @@ android_library {
"SettingsLibSettingsTransition",
"SettingsLibButtonPreference",
"SettingsLibDeviceStateRotationLock",
+ "SettingsLibProfileSelector",
"setupdesign",
"zxing-core-1.7",
"androidx.room_room-runtime",
diff --git a/packages/SettingsLib/ProfileSelector/Android.bp b/packages/SettingsLib/ProfileSelector/Android.bp
new file mode 100644
index 000000000000..250cd755a6a6
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/Android.bp
@@ -0,0 +1,27 @@
+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"],
+}
+
+android_library {
+ name: "SettingsLibProfileSelector",
+
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "com.google.android.material_material",
+ "SettingsLibSettingsTheme",
+ ],
+
+ sdk_version: "system_current",
+ min_sdk_version: "23",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.mediaprovider",
+ ],
+}
diff --git a/packages/SettingsLib/ProfileSelector/AndroidManifest.xml b/packages/SettingsLib/ProfileSelector/AndroidManifest.xml
new file mode 100644
index 000000000000..a57469e39eb6
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.widget">
+
+ <uses-sdk android:minSdkVersion="23" />
+</manifest>
diff --git a/packages/SettingsLib/res/color-night-v31/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color-night/settingslib_tabs_indicator_color.xml
index 9a093601a92c..9a093601a92c 100644
--- a/packages/SettingsLib/res/color-night-v31/settingslib_tabs_indicator_color.xml
+++ b/packages/SettingsLib/ProfileSelector/res/color-night/settingslib_tabs_indicator_color.xml
diff --git a/packages/SettingsLib/res/color-night-v31/settingslib_tabs_text_color.xml b/packages/SettingsLib/ProfileSelector/res/color-night/settingslib_tabs_text_color.xml
index 33f96df8d5b5..33f96df8d5b5 100644
--- a/packages/SettingsLib/res/color-night-v31/settingslib_tabs_text_color.xml
+++ b/packages/SettingsLib/ProfileSelector/res/color-night/settingslib_tabs_text_color.xml
diff --git a/packages/SettingsLib/res/color-v31/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color/settingslib_tabs_indicator_color.xml
index 57fef52f15bd..57fef52f15bd 100644
--- a/packages/SettingsLib/res/color-v31/settingslib_tabs_indicator_color.xml
+++ b/packages/SettingsLib/ProfileSelector/res/color/settingslib_tabs_indicator_color.xml
diff --git a/packages/SettingsLib/res/color-v31/settingslib_tabs_text_color.xml b/packages/SettingsLib/ProfileSelector/res/color/settingslib_tabs_text_color.xml
index df2346d7175e..df2346d7175e 100644
--- a/packages/SettingsLib/res/color-v31/settingslib_tabs_text_color.xml
+++ b/packages/SettingsLib/ProfileSelector/res/color/settingslib_tabs_text_color.xml
diff --git a/packages/SettingsLib/res/drawable-v31/settingslib_tabs_background.xml b/packages/SettingsLib/ProfileSelector/res/drawable/settingslib_tabs_background.xml
index 5378eeef97e0..5378eeef97e0 100644
--- a/packages/SettingsLib/res/drawable-v31/settingslib_tabs_background.xml
+++ b/packages/SettingsLib/ProfileSelector/res/drawable/settingslib_tabs_background.xml
diff --git a/packages/SettingsLib/res/drawable-v31/settingslib_tabs_indicator_background.xml b/packages/SettingsLib/ProfileSelector/res/drawable/settingslib_tabs_indicator_background.xml
index 9c10a5b59281..9c10a5b59281 100644
--- a/packages/SettingsLib/res/drawable-v31/settingslib_tabs_indicator_background.xml
+++ b/packages/SettingsLib/ProfileSelector/res/drawable/settingslib_tabs_indicator_background.xml
diff --git a/packages/SettingsLib/ProfileSelector/res/layout/tab_fragment.xml b/packages/SettingsLib/ProfileSelector/res/layout/tab_fragment.xml
new file mode 100644
index 000000000000..0448c6c4f467
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/layout/tab_fragment.xml
@@ -0,0 +1,38 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:theme="@style/Theme.MaterialComponents.DayNight"
+ android:id="@+id/tab_container"
+ android:clipToPadding="true"
+ android:clipChildren="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <com.google.android.material.tabs.TabLayout
+ android:id="@+id/tabs"
+ style="@style/SettingsLibTabsStyle"/>
+
+ <androidx.viewpager2.widget.ViewPager2
+ android:id="@+id/view_pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ </androidx.viewpager2.widget.ViewPager2>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/ProfileSelector/res/values/strings.xml b/packages/SettingsLib/ProfileSelector/res/values/strings.xml
new file mode 100644
index 000000000000..68d4047a497c
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?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:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Header for items under the personal user [CHAR LIMIT=30] -->
+ <string name="settingslib_category_personal">Personal</string>
+ <!-- Header for items under the work user [CHAR LIMIT=30] -->
+ <string name="settingslib_category_work">Work</string>
+</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/res/values-v31/styles.xml b/packages/SettingsLib/ProfileSelector/res/values/styles.xml
index 0b703c99884b..0b703c99884b 100644
--- a/packages/SettingsLib/res/values-v31/styles.xml
+++ b/packages/SettingsLib/ProfileSelector/res/values/styles.xml
diff --git a/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileSelectFragment.java b/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileSelectFragment.java
new file mode 100644
index 000000000000..ac426ed8b5d4
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileSelectFragment.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.fragment.app.Fragment;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayoutMediator;
+
+/**
+ * Base fragment class for profile settings.
+ */
+public abstract class ProfileSelectFragment extends Fragment {
+
+ /**
+ * Personal or Work profile tab of {@link ProfileSelectFragment}
+ * <p>0: Personal tab.
+ * <p>1: Work profile tab.
+ */
+ public static final String EXTRA_SHOW_FRAGMENT_TAB =
+ ":settings:show_fragment_tab";
+
+ /**
+ * Used in fragment argument with Extra key EXTRA_SHOW_FRAGMENT_TAB
+ */
+ public static final int PERSONAL_TAB = 0;
+
+ /**
+ * Used in fragment argument with Extra key EXTRA_SHOW_FRAGMENT_TAB
+ */
+ public static final int WORK_TAB = 1;
+
+ private ViewGroup mContentView;
+
+ private ViewPager2 mViewPager;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Defines the xml file for the fragment
+ mContentView = (ViewGroup) inflater.inflate(R.layout.tab_fragment, container, false);
+
+ final Activity activity = getActivity();
+ final int titleResId = getTitleResId();
+ if (titleResId > 0) {
+ activity.setTitle(titleResId);
+ }
+ final int selectedTab = getTabId(activity, getArguments());
+
+ final View tabContainer = mContentView.findViewById(R.id.tab_container);
+ mViewPager = tabContainer.findViewById(R.id.view_pager);
+ mViewPager.setAdapter(new ProfileViewPagerAdapter(this));
+ final TabLayout tabs = tabContainer.findViewById(R.id.tabs);
+ new TabLayoutMediator(tabs, mViewPager,
+ (tab, position) -> tab.setText(getPageTitle(position))
+ ).attach();
+
+ tabContainer.setVisibility(View.VISIBLE);
+ final TabLayout.Tab tab = tabs.getTabAt(selectedTab);
+ tab.select();
+
+ return mContentView;
+ }
+
+ /**
+ * create Personal or Work profile fragment
+ * <p>0: Personal profile.
+ * <p>1: Work profile.
+ */
+ public abstract Fragment createFragment(int position);
+
+ /**
+ * Returns a resource ID of the title
+ * Override this if the title needs to be updated dynamically.
+ */
+ public int getTitleResId() {
+ return 0;
+ }
+
+ int getTabId(Activity activity, Bundle bundle) {
+ if (bundle != null) {
+ final int extraTab = bundle.getInt(EXTRA_SHOW_FRAGMENT_TAB, -1);
+ if (extraTab != -1) {
+ return extraTab;
+ }
+ }
+ return PERSONAL_TAB;
+ }
+
+ private CharSequence getPageTitle(int position) {
+ if (position == WORK_TAB) {
+ return getContext().getString(R.string.settingslib_category_work);
+ }
+
+ return getString(R.string.settingslib_category_personal);
+ }
+}
diff --git a/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileViewPagerAdapter.java b/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileViewPagerAdapter.java
new file mode 100644
index 000000000000..daf2564a674e
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileViewPagerAdapter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget;
+
+import androidx.fragment.app.Fragment;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+/**
+ * ViewPager Adapter to handle between TabLayout and ViewPager2
+ */
+public class ProfileViewPagerAdapter extends FragmentStateAdapter {
+
+ private final ProfileSelectFragment mParentFragments;
+
+ ProfileViewPagerAdapter(ProfileSelectFragment fragment) {
+ super(fragment);
+ mParentFragments = fragment;
+ }
+
+ @Override
+ public Fragment createFragment(int position) {
+ return mParentFragments.createFragment(position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return 2;
+ }
+}
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
index bcc64d3cd234..b5a21bdae606 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
@@ -23,5 +23,6 @@ android_library {
apex_available: [
"//apex_available:platform",
"com.android.permission",
+ "com.android.mediaprovider",
],
}
diff --git a/packages/SettingsLib/SettingsTheme/Android.bp b/packages/SettingsLib/SettingsTheme/Android.bp
index 82e0220997d3..939977fa7ef2 100644
--- a/packages/SettingsLib/SettingsTheme/Android.bp
+++ b/packages/SettingsLib/SettingsTheme/Android.bp
@@ -24,5 +24,6 @@ android_library {
"com.android.permission",
"com.android.adservices",
"com.android.healthconnect",
+ "com.android.mediaprovider",
],
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
index 7fd49db93748..04d0fe03e1e8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
@@ -18,10 +18,10 @@ package com.android.settingslib.spa.gallery.home
import android.os.Bundle
import androidx.compose.runtime.Composable
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
@@ -59,9 +59,13 @@ object HomePageProvider : SettingsPageProvider {
)
}
+ override fun getTitle(arguments: Bundle?): String {
+ return SpaEnvironmentFactory.instance.appContext.getString(R.string.app_name)
+ }
+
@Composable
override fun Page(arguments: Bundle?) {
- HomeScaffold(title = stringResource(R.string.app_name)) {
+ HomeScaffold(title = getTitle(arguments)) {
for (entry in buildEntry(arguments)) {
if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
entry.UiLayout(ArgumentPageModel.buildArgument(intParam = 0))
@@ -76,6 +80,7 @@ object HomePageProvider : SettingsPageProvider {
@Preview(showBackground = true)
@Composable
private fun HomeScreenPreview() {
+ SpaEnvironmentFactory.resetForPreview()
SettingsTheme {
HomePageProvider.Page(null)
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index 82073104a3a7..7958d11ad513 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -23,6 +23,7 @@ import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
@@ -98,9 +99,13 @@ object ArgumentPageProvider : SettingsPageProvider {
}
}
+ override fun getTitle(arguments: Bundle?): String {
+ return ArgumentPageModel.genPageTitle()
+ }
+
@Composable
override fun Page(arguments: Bundle?) {
- RegularScaffold(title = ArgumentPageModel.create(arguments).genPageTitle()) {
+ RegularScaffold(title = getTitle(arguments)) {
for (entry in buildEntry(arguments)) {
if (entry.toPage != null) {
entry.UiLayout(ArgumentPageModel.buildNextArgument(arguments))
@@ -115,6 +120,7 @@ object ArgumentPageProvider : SettingsPageProvider {
@Preview(showBackground = true)
@Composable
private fun ArgumentPagePreview() {
+ SpaEnvironmentFactory.resetForPreview()
SettingsTheme {
ArgumentPageProvider.Page(
ArgumentPageModel.buildArgument(stringParam = "foo", intParam = 0)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
index e5e3c679a76a..5f15865ff7b9 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
@@ -81,6 +81,10 @@ class ArgumentPageModel : PageModel() {
return EntrySearchData(title = PAGE_TITLE, keyword = ARGUMENT_PAGE_KEYWORDS)
}
+ fun genPageTitle(): String {
+ return PAGE_TITLE
+ }
+
@Composable
fun create(arguments: Bundle?): ArgumentPageModel {
val pageModel: ArgumentPageModel = viewModel(key = arguments.toString())
@@ -89,7 +93,6 @@ class ArgumentPageModel : PageModel() {
}
}
- private val title = PAGE_TITLE
private var arguments: Bundle? = null
private var stringParam: String? = null
private var intParam: Int? = null
@@ -104,11 +107,6 @@ class ArgumentPageModel : PageModel() {
}
@Composable
- fun genPageTitle(): String {
- return title
- }
-
- @Composable
fun genStringParamPreferenceModel(): PreferenceModel {
return object : PreferenceModel {
override val title = STRING_PARAM_TITLE
@@ -131,7 +129,7 @@ class ArgumentPageModel : PageModel() {
"$INT_PARAM_NAME=" + intParam!!
)
return object : PreferenceModel {
- override val title = genPageTitle()
+ override val title = PAGE_TITLE
override val summary = stateOf(summaryArray.joinToString(", "))
override val onClick = navigator(
SettingsPageProviderEnum.ARGUMENT.displayName + parameter.navLink(arguments)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
index 0fc2a5f5fda8..c903cfd96ce3 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
@@ -67,9 +67,13 @@ object FooterPageProvider : SettingsPageProvider {
}
}
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+
@Composable
override fun Page(arguments: Bundle?) {
- RegularScaffold(title = TITLE) {
+ RegularScaffold(title = getTitle(arguments)) {
for (entry in buildEntry(arguments)) {
entry.UiLayout()
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
index a64d4a5d3ea6..e10cf3aa0702 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
@@ -31,7 +31,6 @@ import com.android.settingslib.spa.widget.IllustrationModel
import com.android.settingslib.spa.widget.ResourceType
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
private const val TITLE = "Sample Illustration"
@@ -82,13 +81,8 @@ object IllustrationPageProvider : SettingsPageProvider {
}
}
- @Composable
- override fun Page(arguments: Bundle?) {
- RegularScaffold(title = TITLE) {
- for (entry in buildEntry(arguments)) {
- entry.UiLayout()
- }
- }
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
index dc45df4a0374..9136b0430c40 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
@@ -60,6 +60,10 @@ object ProgressBarPageProvider : SettingsPageProvider {
}
}
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+
@Composable
override fun Page(arguments: Bundle?) {
// Mocks a loading time of 2 seconds.
@@ -69,7 +73,7 @@ object ProgressBarPageProvider : SettingsPageProvider {
loading = false
}
- RegularScaffold(title = TITLE) {
+ RegularScaffold(title = getTitle(arguments)) {
// Auto update the progress and finally jump tp 0.4f.
var progress by remember { mutableStateOf(0f) }
LaunchedEffect(Unit) {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
index b38178b0e6f4..cb58a95e01a6 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
@@ -49,9 +49,13 @@ object SettingsPagerPageProvider : SettingsPageProvider {
}
}
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+
@Composable
override fun Page(arguments: Bundle?) {
- SettingsScaffold(title = TITLE) { paddingValues ->
+ SettingsScaffold(title = getTitle(arguments)) { paddingValues ->
Box(Modifier.padding(paddingValues)) {
SettingsPager(listOf("Personal", "Work")) {
PlaceholderTitle("Page $it")
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
index 7567c6daf996..73b34a50a520 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
@@ -37,7 +37,6 @@ import com.android.settingslib.spa.widget.preference.SliderPreference
import com.android.settingslib.spa.widget.preference.SliderPreferenceModel
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
private const val TITLE = "Sample Slider"
@@ -119,13 +118,8 @@ object SliderPageProvider : SettingsPageProvider {
}
}
- @Composable
- override fun Page(arguments: Bundle?) {
- RegularScaffold(title = TITLE) {
- for (entry in buildEntry(arguments)) {
- entry.UiLayout()
- }
- }
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
index a8e49384da97..f38a8d4193b3 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
@@ -33,7 +33,6 @@ import com.android.settingslib.spa.widget.preference.MainSwitchPreference
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
private const val TITLE = "Sample MainSwitchPreference"
@@ -72,13 +71,8 @@ object MainSwitchPreferencePageProvider : SettingsPageProvider {
}
}
- @Composable
- override fun Page(arguments: Bundle?) {
- RegularScaffold(title = TITLE) {
- for (entry in buildEntry(arguments)) {
- entry.UiLayout()
- }
- }
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
index 165eaa05c9d5..61925a7b7164 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
@@ -17,7 +17,6 @@
package com.android.settingslib.spa.gallery.preference
import android.os.Bundle
-import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -25,7 +24,6 @@ import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
private const val TITLE = "Category: Preference"
@@ -54,12 +52,7 @@ object PreferenceMainPageProvider : SettingsPageProvider {
}
}
- @Composable
- override fun Page(arguments: Bundle?) {
- RegularScaffold(title = TITLE) {
- for (entry in buildEntry(arguments)) {
- entry.UiLayout()
- }
- }
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index fa8d51c3561f..26e59ff699a4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -49,7 +49,6 @@ import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Compan
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.SettingsIcon
private const val TAG = "PreferencePage"
@@ -128,11 +127,11 @@ object PreferencePageProvider : SettingsPageProvider {
.setStatusDataFn { EntryStatusData(isDisabled = false) }
.setUiLayoutFn {
val model = PreferencePageModel.create()
- val asyncSummary = remember { model.getAsyncSummary() }
Preference(
object : PreferenceModel {
override val title = ASYNC_PREFERENCE_TITLE
- override val summary = asyncSummary
+ override val summary = model.asyncSummary
+ override val enabled = model.asyncEnable
}
)
}.build()
@@ -204,19 +203,15 @@ object PreferencePageProvider : SettingsPageProvider {
}
}
- @Composable
- override fun Page(arguments: Bundle?) {
- RegularScaffold(title = PAGE_TITLE) {
- for (entry in buildEntry(arguments)) {
- entry.UiLayout()
- }
- }
+ override fun getTitle(arguments: Bundle?): String {
+ return PAGE_TITLE
}
}
@Preview(showBackground = true)
@Composable
private fun PreferencePagePreview() {
+ SpaEnvironmentFactory.resetForPreview()
SettingsTheme {
PreferencePageProvider.Page(null)
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
index 1e64b2edb60b..d874417c1a56 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
@@ -59,7 +59,8 @@ class PreferencePageModel : PageModel() {
private val spaLogger = SpaEnvironmentFactory.instance.logger
- private val asyncSummary = mutableStateOf(" ")
+ val asyncSummary = mutableStateOf("(loading)")
+ val asyncEnable = mutableStateOf(false)
private val manualUpdater = mutableStateOf(0)
@@ -87,16 +88,13 @@ class PreferencePageModel : PageModel() {
override fun initialize(arguments: Bundle?) {
spaLogger.message(TAG, "initialize with args " + arguments.toString())
viewModelScope.launch(Dispatchers.IO) {
+ // Loading your data here.
delay(2000L)
asyncSummary.value = ASYNC_PREFERENCE_SUMMARY
+ asyncEnable.value = true
}
}
- fun getAsyncSummary(): State<String> {
- spaLogger.message(TAG, "getAsyncSummary")
- return asyncSummary
- }
-
fun getManualUpdaterSummary(): State<String> {
spaLogger.message(TAG, "getManualUpdaterSummary")
return derivedStateOf { manualUpdater.value.toString() }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
index 46b44ca9d614..367766a34a3c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
@@ -34,7 +34,6 @@ import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import kotlinx.coroutines.delay
private const val TITLE = "Sample SwitchPreference"
@@ -88,13 +87,8 @@ object SwitchPreferencePageProvider : SettingsPageProvider {
}
}
- @Composable
- override fun Page(arguments: Bundle?) {
- RegularScaffold(title = TITLE) {
- for (entry in buildEntry(arguments)) {
- entry.UiLayout()
- }
- }
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
index b991f59866eb..22da99c23fc8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
@@ -34,7 +34,6 @@ import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import kotlinx.coroutines.delay
private const val TITLE = "Sample TwoTargetSwitchPreference"
@@ -88,13 +87,8 @@ object TwoTargetSwitchPreferencePageProvider : SettingsPageProvider {
}
}
- @Composable
- override fun Page(arguments: Bundle?) {
- RegularScaffold(title = TITLE) {
- for (entry in buildEntry(arguments)) {
- entry.UiLayout()
- }
- }
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
index a4713b993af0..d87cbe82d9d8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
@@ -48,41 +48,40 @@ object CategoryPageProvider : SettingsPageProvider {
}
}
- @Composable
- override fun Page(arguments: Bundle?) {
- CategoryPage()
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
}
-}
-@Composable
-private fun CategoryPage() {
- RegularScaffold(title = TITLE) {
- CategoryTitle("Category A")
- Preference(remember {
- object : PreferenceModel {
- override val title = "Preference 1"
- override val summary = stateOf("Summary 1")
- }
- })
- Preference(remember {
- object : PreferenceModel {
- override val title = "Preference 2"
- override val summary = stateOf("Summary 2")
- }
- })
- Category("Category B") {
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(title = getTitle(arguments)) {
+ CategoryTitle("Category A")
Preference(remember {
object : PreferenceModel {
- override val title = "Preference 3"
- override val summary = stateOf("Summary 3")
+ override val title = "Preference 1"
+ override val summary = stateOf("Summary 1")
}
})
Preference(remember {
object : PreferenceModel {
- override val title = "Preference 4"
- override val summary = stateOf("Summary 4")
+ override val title = "Preference 2"
+ override val summary = stateOf("Summary 2")
}
})
+ Category("Category B") {
+ Preference(remember {
+ object : PreferenceModel {
+ override val title = "Preference 3"
+ override val summary = stateOf("Summary 3")
+ }
+ })
+ Preference(remember {
+ object : PreferenceModel {
+ override val title = "Preference 4"
+ override val summary = stateOf("Summary 4")
+ }
+ })
+ }
}
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
index 03b72d348d40..ec2f436c9e98 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
@@ -49,9 +49,13 @@ object SpinnerPageProvider : SettingsPageProvider {
}
}
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+
@Composable
override fun Page(arguments: Bundle?) {
- RegularScaffold(title = TITLE) {
+ RegularScaffold(title = getTitle(arguments)) {
val selectedIndex = rememberSaveable { mutableStateOf(0) }
Spinner(
options = (1..3).map { "Option $it" },
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index f8963b2a8837..151b50cdb5c4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -19,6 +19,7 @@ package com.android.settingslib.spa.framework.common
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
/**
* An SettingsPageProvider which is used to create Settings page instances.
@@ -36,13 +37,19 @@ interface SettingsPageProvider {
val parameter: List<NamedNavArgument>
get() = emptyList()
- /** The [Composable] used to render this page. */
- @Composable
- fun Page(arguments: Bundle?)
+ fun getTitle(arguments: Bundle?): String = displayName ?: name
fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()
- fun getTitle(arguments: Bundle?): String = displayName ?: name
+ /** The [Composable] used to render this page. */
+ @Composable
+ fun Page(arguments: Bundle?) {
+ RegularScaffold(title = getTitle(arguments)) {
+ for (entry in buildEntry(arguments)) {
+ entry.UiLayout()
+ }
+ }
+ }
}
fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index 0973ba593904..b83104360260 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -19,6 +19,8 @@ package com.android.settingslib.spa.framework.common
import android.app.Activity
import android.content.Context
import android.util.Log
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
private const val TAG = "SpaEnvironment"
@@ -30,6 +32,20 @@ object SpaEnvironmentFactory {
Log.d(TAG, "reset")
}
+ @Composable
+ fun resetForPreview() {
+ val context = LocalContext.current
+ spaEnvironment = object : SpaEnvironment(context) {
+ override val pageProviderRepository = lazy {
+ SettingsPageProviderRepository(
+ allPageProviders = emptyList(),
+ rootPages = emptyList()
+ )
+ }
+ }
+ Log.d(TAG, "resetForPreview")
+ }
+
val instance: SpaEnvironment
get() {
if (spaEnvironment == null)
@@ -38,11 +54,14 @@ object SpaEnvironmentFactory {
}
}
-abstract class SpaEnvironment(val context: Context) {
+abstract class SpaEnvironment(context: Context) {
abstract val pageProviderRepository: Lazy<SettingsPageProviderRepository>
val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
+ // In Robolectric test, applicationContext is not available. Use context as fallback.
+ val appContext: Context = context.applicationContext ?: context
+
open val browseActivityClass: Class<out Activity>? = null
open val entryProviderAuthorities: String? = null
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
new file mode 100644
index 000000000000..21ff08515ee6
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import androidx.core.os.bundleOf
+import androidx.navigation.NamedNavArgument
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class ParameterTest {
+ @Test
+ fun navRouteTest() {
+ val navArguments = listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ )
+
+ val route = navArguments.navRoute()
+ assertThat(route).isEqualTo("/{string_param}/{int_param}")
+ }
+
+ @Test
+ fun navLinkTest() {
+ val navArguments = listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ )
+
+ val unsetAllLink = navArguments.navLink()
+ assertThat(unsetAllLink).isEqualTo("/[unset]/[unset]")
+
+ val setAllLink = navArguments.navLink(
+ bundleOf(
+ "string_param" to "myStr",
+ "int_param" to 10,
+ )
+ )
+ assertThat(setAllLink).isEqualTo("/myStr/10")
+
+ val setUnknownLink = navArguments.navLink(
+ bundleOf(
+ "string_param" to "myStr",
+ "int_param" to 10,
+ "unknown_param" to "unknown",
+ )
+ )
+ assertThat(setUnknownLink).isEqualTo("/myStr/10")
+
+ val setWrongTypeLink = navArguments.navLink(
+ bundleOf(
+ "string_param" to "myStr",
+ "int_param" to "wrongStr",
+ )
+ )
+ assertThat(setWrongTypeLink).isEqualTo("/myStr/0")
+ }
+
+ @Test
+ fun normalizeTest() {
+ val emptyArguments = emptyList<NamedNavArgument>()
+ assertThat(emptyArguments.normalize()).isNull()
+
+ val navArguments = listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ navArgument("rt_param") { type = NavType.StringType },
+ )
+
+ val emptyParam = navArguments.normalize()
+ assertThat(emptyParam).isNotNull()
+ assertThat(emptyParam.toString()).isEqualTo(
+ "Bundle[{rt_param=null, unset_string_param=null, unset_int_param=null}]"
+ )
+
+ val setParialParam = navArguments.normalize(
+ bundleOf(
+ "string_param" to "myStr",
+ "rt_param" to "rtStr",
+ )
+ )
+ assertThat(setParialParam).isNotNull()
+ assertThat(setParialParam.toString()).isEqualTo(
+ "Bundle[{rt_param=null, string_param=myStr, unset_int_param=null}]"
+ )
+
+ val setAllParam = navArguments.normalize(
+ bundleOf(
+ "string_param" to "myStr",
+ "int_param" to 10,
+ "rt_param" to "rtStr",
+ )
+ )
+ assertThat(setAllParam).isNotNull()
+ assertThat(setAllParam.toString()).isEqualTo(
+ "Bundle[{rt_param=null, int_param=10, string_param=myStr}]"
+ )
+ }
+
+ @Test
+ fun getArgTest() {
+ val navArguments = listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ )
+
+ assertThat(
+ navArguments.getStringArg(
+ "string_param", bundleOf(
+ "string_param" to "myStr",
+ )
+ )
+ ).isEqualTo("myStr")
+
+ assertThat(
+ navArguments.getStringArg(
+ "string_param", bundleOf(
+ "string_param" to 10,
+ )
+ )
+ ).isNull()
+
+ assertThat(
+ navArguments.getStringArg(
+ "unknown_param", bundleOf(
+ "string_param" to "myStr",
+ )
+ )
+ ).isNull()
+
+ assertThat(navArguments.getStringArg("string_param")).isNull()
+
+ assertThat(
+ navArguments.getIntArg(
+ "int_param", bundleOf(
+ "int_param" to 10,
+ )
+ )
+ ).isEqualTo(10)
+
+ assertThat(
+ navArguments.getIntArg(
+ "int_param", bundleOf(
+ "int_param" to "10",
+ )
+ )
+ ).isEqualTo(0)
+
+ assertThat(
+ navArguments.getIntArg(
+ "unknown_param", bundleOf(
+ "int_param" to 10,
+ )
+ )
+ ).isNull()
+
+ assertThat(navArguments.getIntArg("int_param")).isNull()
+ }
+
+ @Test
+ fun isRuntimeParamTest() {
+ val regularParam = navArgument("regular_param") { type = NavType.StringType }
+ val rtParam = navArgument("rt_param") { type = NavType.StringType }
+ assertThat(regularParam.isRuntimeParam()).isFalse()
+ assertThat(rtParam.isRuntimeParam()).isTrue()
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
index fd723dd067d7..bb1cd6e44867 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
@@ -22,6 +22,7 @@ import android.app.admin.DevicePolicyManager
import android.app.usage.StorageStatsManager
import android.apphibernation.AppHibernationManager
import android.content.Context
+import android.content.pm.CrossProfileApps
import android.content.pm.verify.domain.DomainVerificationManager
import android.os.UserHandle
import android.os.UserManager
@@ -36,6 +37,9 @@ val Context.appHibernationManager get() = getSystemService(AppHibernationManager
/** The [AppOpsManager] instance. */
val Context.appOpsManager get() = getSystemService(AppOpsManager::class.java)!!
+/** The [CrossProfileApps] instance. */
+val Context.crossProfileApps get() = getSystemService(CrossProfileApps::class.java)!!
+
/** The [DevicePolicyManager] instance. */
val Context.devicePolicyManager get() = getSystemService(DevicePolicyManager::class.java)!!
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExt.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExt.kt
index a22d1feade0a..2b2f11c7e23f 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExt.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExt.kt
@@ -1,6 +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.settingslib.spaprivileged.model.app
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
/**
* Checks if a package is system module.
@@ -12,3 +32,18 @@ fun PackageManager.isSystemModule(packageName: String): Boolean = try {
// Expected, not system module
false
}
+
+/**
+ * Resolves the activity to start for a given application and action.
+ */
+fun PackageManager.resolveActionForApp(
+ app: ApplicationInfo,
+ action: String,
+ flags: Int = 0,
+): ActivityInfo? {
+ val intent = Intent(action).apply {
+ `package` = app.packageName
+ }
+ return resolveActivityAsUser(intent, ResolveInfoFlags.of(flags.toLong()), app.userId)
+ ?.activityInfo
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
new file mode 100644
index 000000000000..4207490cba80
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.content.ComponentName
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.ModuleInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class PackageManagerExtTest {
+ @JvmField
+ @Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Mock
+ private lateinit var packageManager: PackageManager
+
+ private fun mockResolveActivityAsUser(resolveInfo: ResolveInfo?) {
+ whenever(
+ packageManager.resolveActivityAsUser(any(), any<ResolveInfoFlags>(), eq(APP.userId))
+ ).thenReturn(resolveInfo)
+ }
+
+ @Test
+ fun isSystemModule_whenSystemModule_returnTrue() {
+ whenever(packageManager.getModuleInfo(PACKAGE_NAME, 0)).thenReturn(ModuleInfo())
+
+ val isSystemModule = packageManager.isSystemModule(PACKAGE_NAME)
+
+ assertThat(isSystemModule).isTrue()
+ }
+
+ @Test
+ fun isSystemModule_whenNotSystemModule_returnFalse() {
+ whenever(packageManager.getModuleInfo(PACKAGE_NAME, 0)).thenThrow(NameNotFoundException())
+
+ val isSystemModule = packageManager.isSystemModule(PACKAGE_NAME)
+
+ assertThat(isSystemModule).isFalse()
+ }
+
+ @Test
+ fun resolveActionForApp_noResolveInfo() {
+ mockResolveActivityAsUser(null)
+
+ val activityInfo = packageManager.resolveActionForApp(APP, ACTION)
+
+ assertThat(activityInfo).isNull()
+ }
+
+ @Test
+ fun resolveActionForApp_noActivityInfo() {
+ mockResolveActivityAsUser(ResolveInfo())
+
+ val activityInfo = packageManager.resolveActionForApp(APP, ACTION)
+
+ assertThat(activityInfo).isNull()
+ }
+
+ @Test
+ fun resolveActionForApp_hasActivityInfo() {
+ mockResolveActivityAsUser(ResolveInfo().apply {
+ activityInfo = ActivityInfo().apply {
+ packageName = PACKAGE_NAME
+ name = ACTIVITY_NAME
+ }
+ })
+
+ val activityInfo = packageManager.resolveActionForApp(APP, ACTION)!!
+
+ assertThat(activityInfo.componentName).isEqualTo(ComponentName(PACKAGE_NAME, ACTIVITY_NAME))
+ }
+
+ @Test
+ fun resolveActionForApp_withFlags() {
+ packageManager.resolveActionForApp(
+ app = APP,
+ action = ACTION,
+ flags = PackageManager.GET_META_DATA,
+ )
+
+ val flagsCaptor = ArgumentCaptor.forClass(ResolveInfoFlags::class.java)
+ verify(packageManager).resolveActivityAsUser(any(), flagsCaptor.capture(), eq(APP.userId))
+ assertThat(flagsCaptor.value.value).isEqualTo(PackageManager.GET_META_DATA.toLong())
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "package.name"
+ const val ACTIVITY_NAME = "ActivityName"
+ const val ACTION = "action"
+ const val UID = 123
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ uid = UID
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/OWNERS b/packages/SettingsLib/src/com/android/settingslib/qrcode/OWNERS
new file mode 100644
index 000000000000..61c73fb733a9
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/OWNERS
@@ -0,0 +1,8 @@
+# Default reviewers for this and subdirectories.
+bonianchen@google.com
+changbetty@google.com
+goldmanj@google.com
+wengsu@google.com
+zoeychen@google.com
+
+# Emergency approvers in case the above are not available
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index def7ddc86556..98af15a85238 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -122,6 +122,7 @@ public class SecureSettings {
Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME,
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index cde4bc42b225..80af69c32b05 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -177,6 +177,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 0f037e40b997..06ea381d0c1d 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -27,8 +27,6 @@
-packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
-packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
-packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionDarkness.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
-packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
-packages/SystemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt
-packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
@@ -683,8 +681,6 @@
-packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
-packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
-packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt
-packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
index c2c79cb0f34b..78884ffbe1a2 100644
--- a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
@@ -14,58 +14,84 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.systemui.user.UserSwitcherRootView
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/user_switcher_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginVertical="40dp"
- android:layout_marginHorizontal="60dp">
+ android:orientation="vertical">
- <androidx.constraintlayout.helper.widget.Flow
- android:id="@+id/flow"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:flow_horizontalBias="0.5"
- app:flow_verticalAlign="center"
- app:flow_wrapMode="chain"
- app:flow_horizontalGap="@dimen/user_switcher_fullscreen_horizontal_gap"
- app:flow_verticalGap="44dp"
- app:flow_horizontalStyle="packed"/>
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:fillViewport="true">
- <TextView
- android:id="@+id/cancel"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center"
- app:layout_constraintHeight_min="48dp"
- app:layout_constraintEnd_toStartOf="@+id/add"
- app:layout_constraintBottom_toBottomOf="parent"
- android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding"
- android:textSize="@dimen/user_switcher_fullscreen_button_text_size"
- android:textColor="?androidprv:attr/colorAccentPrimary"
- android:text="@string/cancel" />
+ <com.android.systemui.user.UserSwitcherRootView
+ android:id="@+id/user_switcher_grid_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="40dp"
+ android:paddingHorizontal="60dp">
- <TextView
- android:id="@+id/add"
- style="@style/Widget.Dialog.Button.BorderButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center"
- android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding"
- android:text="@string/add"
- android:textColor="?androidprv:attr/colorAccentPrimary"
- android:textSize="@dimen/user_switcher_fullscreen_button_text_size"
- android:visibility="gone"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHeight_min="48dp" />
-</com.android.systemui.user.UserSwitcherRootView>
+ <androidx.constraintlayout.helper.widget.Flow
+ android:id="@+id/flow"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:flow_horizontalBias="0.5"
+ app:flow_verticalAlign="center"
+ app:flow_wrapMode="chain"
+ app:flow_horizontalGap="@dimen/user_switcher_fullscreen_horizontal_gap"
+ app:flow_verticalGap="44dp"
+ app:flow_horizontalStyle="packed"/>
+ </com.android.systemui.user.UserSwitcherRootView>
+
+ </ScrollView>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="96dp"
+ android:orientation="horizontal"
+ android:gravity="center_vertical|end"
+ android:paddingEnd="48dp">
+
+ <TextView
+ android:id="@+id/cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:minHeight="48dp"
+ android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding"
+ android:textSize="@dimen/user_switcher_fullscreen_button_text_size"
+ android:textColor="?androidprv:attr/colorAccentPrimary"
+ android:text="@string/cancel" />
+
+ <Space
+ android:layout_width="24dp"
+ android:layout_height="0dp"
+ />
+
+ <TextView
+ android:id="@+id/add"
+ style="@style/Widget.Dialog.Button.BorderButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding"
+ android:text="@string/add"
+ android:textColor="?androidprv:attr/colorAccentPrimary"
+ android:textSize="@dimen/user_switcher_fullscreen_button_text_size"
+ android:visibility="gone"
+ android:minHeight="48dp" />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 4613e8b1060f..e743ec87bd1c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -110,4 +110,9 @@ oneway interface IOverviewProxy {
* Sent when screen started turning off.
*/
void onScreenTurningOff() = 24;
+
+ /**
+ * Sent when split keyboard shortcut is triggered to enter stage split.
+ */
+ void enterStageSplitFromRunningApp(boolean leftOrTop) = 25;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
index cd4b9994ccca..0ee813b84402 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
@@ -24,15 +24,13 @@ import com.android.systemui.shared.navigationbar.RegionSamplingHelper.SamplingCa
import java.io.PrintWriter
import java.util.concurrent.Executor
-/**
- * Class for instance of RegionSamplingHelper
- */
-open class RegionSamplingInstance(
- sampledView: View?,
- mainExecutor: Executor?,
- bgExecutor: Executor?,
- regionSamplingEnabled: Boolean,
- updateFun: UpdateColorCallback
+/** Class for instance of RegionSamplingHelper */
+open class RegionSampler(
+ sampledView: View?,
+ mainExecutor: Executor?,
+ bgExecutor: Executor?,
+ regionSamplingEnabled: Boolean,
+ updateFun: UpdateColorCallback
) {
private var regionDarkness = RegionDarkness.DEFAULT
private var samplingBounds = Rect()
@@ -40,23 +38,13 @@ open class RegionSamplingInstance(
@VisibleForTesting var regionSampler: RegionSamplingHelper? = null
private var lightForegroundColor = Color.WHITE
private var darkForegroundColor = Color.BLACK
- /**
- * Interface for method to be passed into RegionSamplingHelper
- */
- @FunctionalInterface
- interface UpdateColorCallback {
- /**
- * Method to update the foreground colors after clock darkness changed.
- */
- fun updateColors()
- }
@VisibleForTesting
open fun createRegionSamplingHelper(
- sampledView: View,
- callback: SamplingCallback,
- mainExecutor: Executor?,
- bgExecutor: Executor?
+ sampledView: View,
+ callback: SamplingCallback,
+ mainExecutor: Executor?,
+ bgExecutor: Executor?
): RegionSamplingHelper {
return RegionSamplingHelper(sampledView, callback, mainExecutor, bgExecutor)
}
@@ -77,7 +65,7 @@ open class RegionSamplingInstance(
*
* @return the determined foreground color
*/
- fun currentForegroundColor(): Int{
+ fun currentForegroundColor(): Int {
return if (regionDarkness.isDark) {
lightForegroundColor
} else {
@@ -97,41 +85,37 @@ open class RegionSamplingInstance(
return regionDarkness
}
- /**
- * Start region sampler
- */
+ /** Start region sampler */
fun startRegionSampler() {
regionSampler?.start(samplingBounds)
}
- /**
- * Stop region sampler
- */
+ /** Stop region sampler */
fun stopRegionSampler() {
regionSampler?.stop()
}
- /**
- * Dump region sampler
- */
+ /** Dump region sampler */
fun dump(pw: PrintWriter) {
regionSampler?.dump(pw)
}
init {
if (regionSamplingEnabled && sampledView != null) {
- regionSampler = createRegionSamplingHelper(sampledView,
+ regionSampler =
+ createRegionSamplingHelper(
+ sampledView,
object : SamplingCallback {
override fun onRegionDarknessChanged(isRegionDark: Boolean) {
regionDarkness = convertToClockDarkness(isRegionDark)
- updateFun.updateColors()
+ updateFun()
}
/**
- * The method getLocationOnScreen is used to obtain the view coordinates
- * relative to its left and top edges on the device screen.
- * Directly accessing the X and Y coordinates of the view returns the
- * location relative to its parent view instead.
- */
+ * The method getLocationOnScreen is used to obtain the view coordinates
+ * relative to its left and top edges on the device screen. Directly
+ * accessing the X and Y coordinates of the view returns the location
+ * relative to its parent view instead.
+ */
override fun getSampledRegion(sampledView: View): Rect {
val screenLocation = tmpScreenLocation
sampledView.getLocationOnScreen(screenLocation)
@@ -147,8 +131,13 @@ open class RegionSamplingInstance(
override fun isSamplingEnabled(): Boolean {
return regionSamplingEnabled
}
- }, mainExecutor, bgExecutor)
+ },
+ mainExecutor,
+ bgExecutor
+ )
}
regionSampler?.setWindowVisible(true)
}
}
+
+typealias UpdateColorCallback = () -> Unit
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 40a96b060bc0..71e04468c07a 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -38,21 +38,21 @@ import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.dagger.KeyguardClockLog
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.shared.regionsampling.RegionSamplingInstance
+import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController
-import java.io.PrintWriter
-import java.util.Locale
-import java.util.TimeZone
-import java.util.concurrent.Executor
-import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+import java.util.concurrent.Executor
+import javax.inject.Inject
/**
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
@@ -69,14 +69,17 @@ open class ClockEventController @Inject constructor(
private val context: Context,
@Main private val mainExecutor: Executor,
@Background private val bgExecutor: Executor,
- @KeyguardClockLog private val logBuffer: LogBuffer,
+ @KeyguardClockLog private val logBuffer: LogBuffer?,
private val featureFlags: FeatureFlags
) {
var clock: ClockController? = null
set(value) {
field = value
if (value != null) {
- value.setLogBuffer(logBuffer)
+ if (logBuffer != null) {
+ value.setLogBuffer(logBuffer)
+ }
+
value.initialize(resources, dozeAmount, 0f)
updateRegionSamplers(value)
}
@@ -139,21 +142,17 @@ open class ClockEventController @Inject constructor(
bgExecutor: Executor?,
regionSamplingEnabled: Boolean,
updateColors: () -> Unit
- ): RegionSamplingInstance {
- return RegionSamplingInstance(
+ ): RegionSampler {
+ return RegionSampler(
sampledView,
mainExecutor,
bgExecutor,
regionSamplingEnabled,
- object : RegionSamplingInstance.UpdateColorCallback {
- override fun updateColors() {
- updateColors()
- }
- })
+ updateFun = { updateColors() } )
}
- var smallRegionSampler: RegionSamplingInstance? = null
- var largeRegionSampler: RegionSamplingInstance? = null
+ var smallRegionSampler: RegionSampler? = null
+ var largeRegionSampler: RegionSampler? = null
private var smallClockIsDark = true
private var largeClockIsDark = true
@@ -161,6 +160,7 @@ open class ClockEventController @Inject constructor(
private val configListener = object : ConfigurationController.ConfigurationListener {
override fun onThemeChanged() {
clock?.events?.onColorPaletteChanged(resources)
+ updateColors()
}
override fun onDensityOrFontScaleChanged() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 71470e8870de..a0206f1f1e70 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -35,6 +35,7 @@ data class KeyguardFingerprintListenModel(
val keyguardOccluded: Boolean,
val occludingAppRequestingFp: Boolean,
val primaryUser: Boolean,
+ val shouldListenSfpsState: Boolean,
val shouldListenForFingerprintAssistant: Boolean,
val switchingUser: Boolean,
val udfps: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 39dc609c9bb7..b84c98d71a28 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -150,6 +150,7 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.settings.SecureSettings;
import com.google.android.collect.Lists;
@@ -321,17 +322,20 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
mCallbacks = Lists.newArrayList();
private ContentObserver mDeviceProvisionedObserver;
+ private ContentObserver mSfpsRequireScreenOnToAuthPrefObserver;
private final ContentObserver mTimeFormatChangeObserver;
private boolean mSwitchingUser;
private boolean mDeviceInteractive;
+ private boolean mSfpsRequireScreenOnToAuthPrefEnabled;
private final SubscriptionManager mSubscriptionManager;
private final TelephonyListenerManager mTelephonyListenerManager;
private final TrustManager mTrustManager;
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final SecureSettings mSecureSettings;
private final InteractionJankMonitor mInteractionJankMonitor;
private final LatencyTracker mLatencyTracker;
private final StatusBarStateController mStatusBarStateController;
@@ -1930,6 +1934,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Context context,
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
+ SecureSettings secureSettings,
DumpManager dumpManager,
@Background Executor backgroundExecutor,
@Main Executor mainExecutor,
@@ -1972,6 +1977,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mStatusBarState = mStatusBarStateController.getState();
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
+ mSecureSettings = secureSettings;
dumpManager.registerDumpable(getClass().getName(), this);
mSensorPrivacyManager = sensorPrivacyManager;
mActiveUnlockConfig = activeUnlockConfiguration;
@@ -2214,9 +2220,37 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Settings.System.TIME_12_24)));
}
};
+
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.TIME_12_24),
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
+
+ if (isSfpsSupported() && isSfpsEnrolled()) {
+ updateSfpsRequireScreenOnToAuthPref();
+ mSfpsRequireScreenOnToAuthPrefObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateSfpsRequireScreenOnToAuthPref();
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ mSecureSettings.getUriFor(
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED),
+ false,
+ mSfpsRequireScreenOnToAuthPrefObserver,
+ getCurrentUser());
+ }
+ }
+
+ protected void updateSfpsRequireScreenOnToAuthPref() {
+ final int defaultSfpsRequireScreenOnToAuthValue =
+ mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0;
+ mSfpsRequireScreenOnToAuthPrefEnabled = mSecureSettings.getIntForUser(
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
+ defaultSfpsRequireScreenOnToAuthValue,
+ getCurrentUser()) != 0;
}
private void initializeSimState() {
@@ -2261,6 +2295,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
/**
+ * @return true if there's at least one sfps enrollment for the current user.
+ */
+ public boolean isSfpsEnrolled() {
+ return mAuthController.isSfpsEnrolled(getCurrentUser());
+ }
+
+ /**
+ * @return true if sfps HW is supported on this device. Can return true even if the user has
+ * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
+ */
+ public boolean isSfpsSupported() {
+ return mAuthController.getSfpsProps() != null
+ && !mAuthController.getSfpsProps().isEmpty();
+ }
+
+ /**
* @return true if there's at least one face enrolled
*/
public boolean isFaceEnrolled() {
@@ -2588,8 +2638,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
&& !isEncryptedOrLockdownForUser
&& userDoesNotHaveTrust);
- final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
- && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
+ boolean shouldListenSfpsState = true;
+ // If mSfpsRequireScreenOnToAuthPrefEnabled, require screen on to listen to SFPS
+ if (isSfpsSupported() && isSfpsEnrolled() && mSfpsRequireScreenOnToAuthPrefEnabled) {
+ shouldListenSfpsState = isDeviceInteractive();
+ }
+
+ boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
+ && shouldListenBouncerState && !isFingerprintLockedOut();
+
+ if (isUdfpsSupported()) {
+ shouldListen = shouldListen && shouldListenUdfpsState;
+ }
+
+ if (isSfpsSupported()) {
+ shouldListen = shouldListen && shouldListenSfpsState;
+ }
maybeLogListenerModelData(
new KeyguardFingerprintListenModel(
@@ -2611,6 +2675,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mKeyguardOccluded,
mOccludingAppRequestingFp,
mIsPrimaryUser,
+ shouldListenSfpsState,
shouldListenForFingerprintAssistant,
mSwitchingUser,
isUdfps,
@@ -3712,6 +3777,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);
}
+ if (mSfpsRequireScreenOnToAuthPrefObserver != null) {
+ mContext.getContentResolver().unregisterContentObserver(
+ mSfpsRequireScreenOnToAuthPrefObserver);
+ }
+
try {
ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
} catch (RemoteException e) {
@@ -3784,6 +3854,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
pw.println(" mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing);
pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState));
pw.println(" mUdfpsBouncerShowing=" + mUdfpsBouncerShowing);
+ } else if (isSfpsSupported()) {
+ pw.println(" sfpsEnrolled=" + isSfpsEnrolled());
+ pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false));
+ if (isSfpsEnrolled()) {
+ pw.println(" mSfpsRequireScreenOnToAuthPrefEnabled="
+ + mSfpsRequireScreenOnToAuthPrefEnabled);
+ }
}
}
if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index d9f44cdecf40..47ee71e8982f 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -43,6 +43,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.NotificationChannels;
import java.util.Comparator;
@@ -137,7 +138,7 @@ public class SystemUIApplication extends Application implements
if (mServicesStarted) {
final int N = mServices.length;
for (int i = 0; i < N; i++) {
- mServices[i].onBootCompleted();
+ notifyBootCompleted(mServices[i]);
}
}
}
@@ -256,7 +257,7 @@ public class SystemUIApplication extends Application implements
for (i = 0; i < mServices.length; i++) {
if (mBootCompleteCache.isBootComplete()) {
- mServices[i].onBootCompleted();
+ notifyBootCompleted(mServices[i]);
}
mDumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
@@ -267,7 +268,13 @@ public class SystemUIApplication extends Application implements
mServicesStarted = true;
}
- private void timeInitialization(String clsName, Runnable init, TimingsTraceLog log,
+ private static void notifyBootCompleted(CoreStartable coreStartable) {
+ Trace.beginSection(coreStartable.getClass().getSimpleName() + ".onBootCompleted()");
+ coreStartable.onBootCompleted();
+ Trace.endSection();
+ }
+
+ private static void timeInitialization(String clsName, Runnable init, TimingsTraceLog log,
String metricsPrefix) {
long ti = System.currentTimeMillis();
log.traceBegin(metricsPrefix + " " + clsName);
@@ -281,11 +288,13 @@ public class SystemUIApplication extends Application implements
}
}
- private CoreStartable startAdditionalStartable(String clsName) {
+ private static CoreStartable startAdditionalStartable(String clsName) {
CoreStartable startable;
if (DEBUG) Log.d(TAG, "loading: " + clsName);
try {
+ Trace.beginSection(clsName + ".newInstance()");
startable = (CoreStartable) Class.forName(clsName).newInstance();
+ Trace.endSection();
} catch (ClassNotFoundException
| IllegalAccessException
| InstantiationException ex) {
@@ -295,14 +304,19 @@ public class SystemUIApplication extends Application implements
return startStartable(startable);
}
- private CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) {
+ private static CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) {
if (DEBUG) Log.d(TAG, "loading: " + clsName);
- return startStartable(provider.get());
+ Trace.beginSection("Provider<" + clsName + ">.get()");
+ CoreStartable startable = provider.get();
+ Trace.endSection();
+ return startStartable(startable);
}
- private CoreStartable startStartable(CoreStartable startable) {
+ private static CoreStartable startStartable(CoreStartable startable) {
if (DEBUG) Log.d(TAG, "running: " + startable);
+ Trace.beginSection(startable.getClass().getSimpleName() + ".start()");
startable.start();
+ Trace.endSection();
return startable;
}
@@ -340,11 +354,18 @@ public class SystemUIApplication extends Application implements
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mServicesStarted) {
- mSysUIComponent.getConfigurationController().onConfigurationChanged(newConfig);
+ ConfigurationController configController = mSysUIComponent.getConfigurationController();
+ Trace.beginSection(
+ configController.getClass().getSimpleName() + ".onConfigurationChanged()");
+ configController.onConfigurationChanged(newConfig);
+ Trace.endSection();
int len = mServices.length;
for (int i = 0; i < len; i++) {
if (mServices[i] != null) {
+ Trace.beginSection(
+ mServices[i].getClass().getSimpleName() + ".onConfigurationChanged()");
mServices[i].onConfigurationChanged(newConfig);
+ Trace.endSection();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 313ff4157155..709e94fa466d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -160,6 +160,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
@NonNull private final SparseBooleanArray mFaceEnrolledForUser;
+ @NonNull private final SparseBooleanArray mSfpsEnrolledForUser;
@NonNull private final SensorPrivacyManager mSensorPrivacyManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private boolean mAllFingerprintAuthenticatorsRegistered;
@@ -365,6 +366,15 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
}
}
}
+ if (mSidefpsProps == null) {
+ Log.d(TAG, "handleEnrollmentsChanged, mSidefpsProps is null");
+ } else {
+ for (FingerprintSensorPropertiesInternal prop : mSidefpsProps) {
+ if (prop.sensorId == sensorId) {
+ mSfpsEnrolledForUser.put(userId, hasEnrollments);
+ }
+ }
+ }
for (Callback cb : mCallbacks) {
cb.onEnrollmentsChanged(modality);
}
@@ -722,6 +732,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
mWindowManager = windowManager;
mInteractionJankMonitor = jankMonitor;
mUdfpsEnrolledForUser = new SparseBooleanArray();
+ mSfpsEnrolledForUser = new SparseBooleanArray();
mFaceEnrolledForUser = new SparseBooleanArray();
mVibratorHelper = vibrator;
@@ -964,6 +975,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
return mUdfpsProps;
}
+ @Nullable
+ public List<FingerprintSensorPropertiesInternal> getSfpsProps() {
+ return mSidefpsProps;
+ }
+
private String getErrorString(@Modality int modality, int error, int vendorCode) {
switch (modality) {
case TYPE_FACE:
@@ -1090,6 +1106,17 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
return mUdfpsEnrolledForUser.get(userId);
}
+ /**
+ * Whether the passed userId has enrolled SFPS.
+ */
+ public boolean isSfpsEnrolled(int userId) {
+ if (mSidefpsController == null) {
+ return false;
+ }
+
+ return mSfpsEnrolledForUser.get(userId);
+ }
+
/** If BiometricPrompt is currently being shown to the user. */
public boolean isShowing() {
return mCurrentDialog != null;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
index ed649b1c0265..f006442906e7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
@@ -181,6 +181,7 @@ constructor(
return enabled == other.enabled &&
name == other.name &&
intent == other.intent &&
- id == other.id
+ id == other.id &&
+ showBroadcastButton == other.showBroadcastButton
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1c0f05745280..2aa70642ac09 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -4729,8 +4729,6 @@ public final class NotificationPanelViewController {
private void startOpening(MotionEvent event) {
updatePanelExpansionAndVisibility();
- // Reset at start so haptic can be triggered as soon as panel starts to open.
- mHasVibratedOnOpen = false;
//TODO: keyguard opens QS a different way; log that too?
// Log the position of the swipe that opened the panel
@@ -5035,7 +5033,7 @@ public final class NotificationPanelViewController {
}
public boolean isFullyExpanded() {
- return mExpandedHeight >= getMaxPanelHeight();
+ return mExpandedHeight >= getMaxPanelTransitionDistance();
}
public boolean isFullyCollapsed() {
@@ -6214,6 +6212,10 @@ public final class NotificationPanelViewController {
}
break;
case MotionEvent.ACTION_MOVE:
+ if (isFullyCollapsed()) {
+ // If panel is fully collapsed, reset haptic effect before adding movement.
+ mHasVibratedOnOpen = false;
+ }
addMovement(event);
if (!isFullyCollapsed()) {
maybeVibrateOnOpening(true /* openingWithTouch */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index fc984618f1b0..58738377a3db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -48,7 +48,8 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.regionsampling.RegionSamplingInstance
+import com.android.systemui.shared.regionsampling.RegionSampler
+import com.android.systemui.shared.regionsampling.UpdateColorCallback
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -90,8 +91,8 @@ class LockscreenSmartspaceController @Inject constructor(
// Smartspace can be used on multiple displays, such as when the user casts their screen
private var smartspaceViews = mutableSetOf<SmartspaceView>()
- private var regionSamplingInstances =
- mutableMapOf<SmartspaceView, RegionSamplingInstance>()
+ private var regionSamplers =
+ mutableMapOf<SmartspaceView, RegionSampler>()
private val regionSamplingEnabled =
featureFlags.isEnabled(Flags.REGION_SAMPLING)
@@ -101,27 +102,23 @@ class LockscreenSmartspaceController @Inject constructor(
private var showSensitiveContentForManagedUser = false
private var managedUserHandle: UserHandle? = null
- private val updateFun = object : RegionSamplingInstance.UpdateColorCallback {
- override fun updateColors() {
- updateTextColorFromRegionSampler()
- }
- }
+ private val updateFun: UpdateColorCallback = { updateTextColorFromRegionSampler() }
// TODO: Move logic into SmartspaceView
var stateChangeListener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
smartspaceViews.add(v as SmartspaceView)
- var regionSamplingInstance = RegionSamplingInstance(
+ var regionSampler = RegionSampler(
v,
uiExecutor,
bgExecutor,
regionSamplingEnabled,
updateFun
)
- initializeTextColors(regionSamplingInstance)
- regionSamplingInstance.startRegionSampler()
- regionSamplingInstances.put(v, regionSamplingInstance)
+ initializeTextColors(regionSampler)
+ regionSampler.startRegionSampler()
+ regionSamplers.put(v, regionSampler)
connectSession()
updateTextColorFromWallpaper()
@@ -131,9 +128,9 @@ class LockscreenSmartspaceController @Inject constructor(
override fun onViewDetachedFromWindow(v: View) {
smartspaceViews.remove(v as SmartspaceView)
- var regionSamplingInstance = regionSamplingInstances.getValue(v)
- regionSamplingInstance.stopRegionSampler()
- regionSamplingInstances.remove(v)
+ var regionSampler = regionSamplers.getValue(v)
+ regionSampler.stopRegionSampler()
+ regionSamplers.remove(v)
if (smartspaceViews.isEmpty()) {
disconnect()
@@ -363,19 +360,19 @@ class LockscreenSmartspaceController @Inject constructor(
}
}
- private fun initializeTextColors(regionSamplingInstance: RegionSamplingInstance) {
+ private fun initializeTextColors(regionSampler: RegionSampler) {
val lightThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_LightWallpaper)
val darkColor = Utils.getColorAttrDefaultColor(lightThemeContext, R.attr.wallpaperTextColor)
val darkThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI)
val lightColor = Utils.getColorAttrDefaultColor(darkThemeContext, R.attr.wallpaperTextColor)
- regionSamplingInstance.setForegroundColors(lightColor, darkColor)
+ regionSampler.setForegroundColors(lightColor, darkColor)
}
private fun updateTextColorFromRegionSampler() {
smartspaceViews.forEach {
- val textColor = regionSamplingInstances.getValue(it).currentForegroundColor()
+ val textColor = regionSamplers.getValue(it).currentForegroundColor()
it.setPrimaryTextColor(textColor)
}
}
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 b16dc5403a57..6a2326036ec0 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
@@ -62,6 +62,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
/**
@@ -136,7 +137,7 @@ constructor(
private val isNewImpl: Boolean
get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
- private val _userSwitcherSettings = MutableStateFlow<UserSwitcherSettingsModel?>(null)
+ private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
_userSwitcherSettings.asStateFlow().filterNotNull()
@@ -235,7 +236,7 @@ constructor(
}
override fun isSimpleUserSwitcher(): Boolean {
- return checkNotNull(_userSwitcherSettings.value?.isSimpleUserSwitcher)
+ return _userSwitcherSettings.value.isSimpleUserSwitcher
}
private fun observeSelectedUser() {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index 07e5cf9d9df2..f9d14cd1b7ca 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -208,7 +208,12 @@ constructor(
if (newGuestId == UserHandle.USER_NULL) {
Log.e(TAG, "Could not create new guest, switching back to system user")
switchUser(UserHandle.USER_SYSTEM)
- withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) }
+ withContext(backgroundDispatcher) {
+ manager.removeUserWhenPossible(
+ UserHandle.of(currentUser.id),
+ /* overrideDevicePolicy= */ false
+ )
+ }
try {
WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null)
} catch (e: RemoteException) {
@@ -222,13 +227,21 @@ constructor(
switchUser(newGuestId)
- withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) }
+ withContext(backgroundDispatcher) {
+ manager.removeUserWhenPossible(
+ UserHandle.of(currentUser.id),
+ /* overrideDevicePolicy= */ false
+ )
+ }
} else {
if (repository.isGuestUserAutoCreated) {
repository.isGuestUserResetting = true
}
switchUser(targetUserId)
- manager.removeUser(currentUser.id)
+ manager.removeUserWhenPossible(
+ UserHandle.of(currentUser.id),
+ /* overrideDevicePolicy= */ false
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index 968af59e6c45..ad09ee3c10d9 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -61,14 +61,15 @@ object UserSwitcherViewBinder {
falsingCollector: FalsingCollector,
onFinish: () -> Unit,
) {
- val rootView: UserSwitcherRootView = view.requireViewById(R.id.user_switcher_root)
- val flowWidget: FlowWidget = view.requireViewById(R.id.flow)
+ val gridContainerView: UserSwitcherRootView =
+ view.requireViewById(R.id.user_switcher_grid_container)
+ val flowWidget: FlowWidget = gridContainerView.requireViewById(R.id.flow)
val addButton: View = view.requireViewById(R.id.add)
val cancelButton: View = view.requireViewById(R.id.cancel)
val popupMenuAdapter = MenuAdapter(layoutInflater)
var popupMenu: UserSwitcherPopupMenu? = null
- rootView.touchHandler =
+ gridContainerView.touchHandler =
object : Gefingerpoken {
override fun onTouchEvent(ev: MotionEvent?): Boolean {
falsingCollector.onTouchEvent(ev)
@@ -134,7 +135,7 @@ object UserSwitcherViewBinder {
val viewPool =
view.children.filter { it.tag == USER_VIEW_TAG }.toMutableList()
viewPool.forEach {
- view.removeView(it)
+ gridContainerView.removeView(it)
flowWidget.removeView(it)
}
users.forEach { userViewModel ->
@@ -152,7 +153,7 @@ object UserSwitcherViewBinder {
inflatedView
}
userView.id = View.generateViewId()
- view.addView(userView)
+ gridContainerView.addView(userView)
flowWidget.addView(userView)
UserViewBinder.bind(
view = userView,
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 44f6d03207b1..ad97ef4a79bc 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -31,6 +31,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UserHandle;
import android.service.wallpaper.WallpaperService;
import android.util.ArraySet;
import android.util.Log;
@@ -598,7 +599,6 @@ public class ImageWallpaper extends WallpaperService {
getDisplayContext().getSystemService(DisplayManager.class)
.unregisterDisplayListener(this);
mWallpaperLocalColorExtractor.cleanUp();
- unloadBitmap();
}
@Override
@@ -676,9 +676,14 @@ public class ImageWallpaper extends WallpaperService {
void drawFrameOnCanvas(Bitmap bitmap) {
Trace.beginSection("ImageWallpaper.CanvasEngine#drawFrame");
Surface surface = mSurfaceHolder.getSurface();
- Canvas canvas = mWideColorGamut
- ? surface.lockHardwareWideColorGamutCanvas()
- : surface.lockHardwareCanvas();
+ Canvas canvas = null;
+ try {
+ canvas = mWideColorGamut
+ ? surface.lockHardwareWideColorGamutCanvas()
+ : surface.lockHardwareCanvas();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Unable to lock canvas", e);
+ }
if (canvas != null) {
Rect dest = mSurfaceHolder.getSurfaceFrame();
try {
@@ -709,17 +714,6 @@ public class ImageWallpaper extends WallpaperService {
}
}
- private void unloadBitmap() {
- mBackgroundExecutor.execute(this::unloadBitmapSynchronized);
- }
-
- private void unloadBitmapSynchronized() {
- synchronized (mLock) {
- mBitmapUsages = 0;
- unloadBitmapInternal();
- }
- }
-
private void unloadBitmapInternal() {
Trace.beginSection("ImageWallpaper.CanvasEngine#unloadBitmap");
if (mBitmap != null) {
@@ -738,7 +732,7 @@ public class ImageWallpaper extends WallpaperService {
boolean loadSuccess = false;
Bitmap bitmap;
try {
- bitmap = mWallpaperManager.getBitmap(false);
+ bitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT, false);
if (bitmap != null
&& bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
throw new RuntimeException("Wallpaper is too large to draw!");
@@ -757,7 +751,7 @@ public class ImageWallpaper extends WallpaperService {
}
try {
- bitmap = mWallpaperManager.getBitmap(false);
+ bitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT, false);
} catch (RuntimeException | OutOfMemoryError e) {
Log.w(TAG, "Unable to load default wallpaper!", e);
bitmap = null;
@@ -770,9 +764,6 @@ public class ImageWallpaper extends WallpaperService {
Log.e(TAG, "Attempt to load a recycled bitmap");
} else if (mBitmap == bitmap) {
Log.e(TAG, "Loaded a bitmap that was already loaded");
- } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
- Log.e(TAG, "Attempt to load an invalid wallpaper of length "
- + bitmap.getWidth() + "x" + bitmap.getHeight());
} else {
// at this point, loading is done correctly.
loadSuccess = true;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 1c3656d71d82..52b6b38ca8ef 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -65,7 +65,6 @@ import org.mockito.junit.MockitoJUnit
class ClockEventControllerTest : SysuiTestCase() {
@JvmField @Rule val mockito = MockitoJUnit.rule()
- @Mock private lateinit var keyguardInteractor: KeyguardInteractor
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var batteryController: BatteryController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index aca60c033bac..131cf7d33e3a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -72,6 +72,7 @@ private fun fingerprintModel(user: Int) = KeyguardFingerprintListenModel(
keyguardOccluded = false,
occludingAppRequestingFp = false,
primaryUser = false,
+ shouldListenSfpsState = false,
shouldListenForFingerprintAssistant = false,
switchingUser = false,
udfps = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 680c3b8f546b..ee480b61ecb4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -21,6 +21,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -39,6 +40,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -54,6 +56,7 @@ import android.app.trust.IStrongAuthTracker;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -61,18 +64,21 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.net.Uri;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -111,6 +117,7 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Assert;
@@ -182,6 +189,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
+ private SecureSettings mSecureSettings;
+ @Mock
private TelephonyManager mTelephonyManager;
@Mock
private SensorPrivacyManager mSensorPrivacyManager;
@@ -215,6 +224,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
private GlobalSettings mGlobalSettings;
private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
+
private final int mCurrentUserId = 100;
private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -224,6 +234,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
+ @Mock
+ private Uri mURI;
+
// Direct executor
private final Executor mBackgroundExecutor = Runnable::run;
private final Executor mMainExecutor = Runnable::run;
@@ -305,6 +318,15 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
+
+ when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
+
+ final ContentResolver contentResolver = mContext.getContentResolver();
+ ExtendedMockito.spyOn(contentResolver);
+ doNothing().when(contentResolver)
+ .registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class),
+ anyInt());
+
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
verify(mBiometricManager)
@@ -1136,6 +1158,67 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ public void testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled()
+ throws RemoteException {
+ // SFPS supported and enrolled
+ setup_SfpsProps();
+
+ // WHEN require screen on to auth is disabled, and keyguard is not awake
+ when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(0);
+ mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true);
+
+ // Preconditions for sfps auth to run
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+
+ statusBarShadeIsLocked();
+ mTestableLooper.processAllMessages();
+
+ // THEN we should listen for sfps when screen off, because require screen on is disabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+
+ // WHEN require screen on to auth is enabled, and keyguard is not awake
+ when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(1);
+ mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+ // THEN we shouldn't listen for sfps when screen off, because require screen on is enabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
+
+ // Device now awake & keyguard is now interactive
+ deviceNotGoingToSleep();
+ deviceIsInteractive();
+ keyguardIsVisible();
+
+ // THEN we should listen for sfps when screen on, and require screen on is enabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+ }
+
+ private void setup_SfpsProps() {
+ final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+ props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
+ when(mAuthController.getSfpsProps()).thenReturn(props);
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+ }
+
+ private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal(
+ @FingerprintSensorProperties.SensorType int sensorType) {
+ return new FingerprintSensorPropertiesInternal(
+ 0 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 1 /* maxEnrollmentsPerUser */,
+ new ArrayList<ComponentInfoInternal>(),
+ sensorType,
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ }
+
+ @Test
public void testShouldNotListenForUdfps_whenTrustEnabled() {
// GIVEN a "we should listen for udfps" state
mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
@@ -1804,7 +1887,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
protected TestableKeyguardUpdateMonitor(Context context) {
super(context,
TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
- mBroadcastDispatcher, mDumpManager,
+ mBroadcastDispatcher, mSecureSettings, mDumpManager,
mBackgroundExecutor, mMainExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index ac4dd49208c2..89c5e59dfb8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -1680,6 +1680,15 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
inOrder.verify(mNotificationStackScrollLayoutController).onExpansionStopped();
}
+ @Test
+ public void inUnlockedSplitShade_transitioningMaxTransitionDistance_makesShadeFullyExpanded() {
+ mStatusBarStateController.setState(SHADE);
+ enableSplitShade(true);
+ int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
+ mNotificationPanelViewController.setExpandedHeight(transitionDistance);
+ assertThat(mNotificationPanelViewController.isFullyExpanded()).isTrue();
+ }
+
private static MotionEvent createMotionEvent(int x, int y, int action) {
return MotionEvent.obtain(
/* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
index 09d51f6447b0..5a62cc18cae3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
@@ -21,61 +21,55 @@ import org.mockito.junit.MockitoJUnit
@RunWith(AndroidTestingRunner::class)
@SmallTest
-class RegionSamplingInstanceTest : SysuiTestCase() {
+class RegionSamplerTest : SysuiTestCase() {
- @JvmField @Rule
- val mockito = MockitoJUnit.rule()
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
@Mock private lateinit var sampledView: View
@Mock private lateinit var mainExecutor: Executor
@Mock private lateinit var bgExecutor: Executor
@Mock private lateinit var regionSampler: RegionSamplingHelper
- @Mock private lateinit var updateFun: RegionSamplingInstance.UpdateColorCallback
@Mock private lateinit var pw: PrintWriter
@Mock private lateinit var callback: RegionSamplingHelper.SamplingCallback
- private lateinit var regionSamplingInstance: RegionSamplingInstance
+ private lateinit var mRegionSampler: RegionSampler
+ private var updateFun: UpdateColorCallback = {}
@Before
fun setUp() {
whenever(sampledView.isAttachedToWindow).thenReturn(true)
- whenever(regionSampler.callback).thenReturn(this@RegionSamplingInstanceTest.callback)
-
- regionSamplingInstance = object : RegionSamplingInstance(
- sampledView,
- mainExecutor,
- bgExecutor,
- true,
- updateFun
- ) {
- override fun createRegionSamplingHelper(
+ whenever(regionSampler.callback).thenReturn(this@RegionSamplerTest.callback)
+
+ mRegionSampler =
+ object : RegionSampler(sampledView, mainExecutor, bgExecutor, true, updateFun) {
+ override fun createRegionSamplingHelper(
sampledView: View,
callback: RegionSamplingHelper.SamplingCallback,
mainExecutor: Executor?,
bgExecutor: Executor?
- ): RegionSamplingHelper {
- return this@RegionSamplingInstanceTest.regionSampler
+ ): RegionSamplingHelper {
+ return this@RegionSamplerTest.regionSampler
+ }
}
- }
}
@Test
fun testStartRegionSampler() {
- regionSamplingInstance.startRegionSampler()
+ mRegionSampler.startRegionSampler()
verify(regionSampler).start(Rect(0, 0, 0, 0))
}
@Test
fun testStopRegionSampler() {
- regionSamplingInstance.stopRegionSampler()
+ mRegionSampler.stopRegionSampler()
verify(regionSampler).stop()
}
@Test
fun testDump() {
- regionSamplingInstance.dump(pw)
+ mRegionSampler.dump(pw)
verify(regionSampler).dump(pw)
}
@@ -91,23 +85,18 @@ class RegionSamplingInstanceTest : SysuiTestCase() {
@Test
fun testFlagFalse() {
- regionSamplingInstance = object : RegionSamplingInstance(
- sampledView,
- mainExecutor,
- bgExecutor,
- false,
- updateFun
- ) {
- override fun createRegionSamplingHelper(
+ mRegionSampler =
+ object : RegionSampler(sampledView, mainExecutor, bgExecutor, false, updateFun) {
+ override fun createRegionSamplingHelper(
sampledView: View,
callback: RegionSamplingHelper.SamplingCallback,
mainExecutor: Executor?,
bgExecutor: Executor?
- ): RegionSamplingHelper {
- return this@RegionSamplingInstanceTest.regionSampler
+ ): RegionSamplingHelper {
+ return this@RegionSamplerTest.regionSampler
+ }
}
- }
- Assert.assertEquals(regionSamplingInstance.regionSampler, null)
+ Assert.assertEquals(mRegionSampler.regionSampler, null)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
deleted file mode 100644
index ab712649a90f..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
+++ /dev/null
@@ -1,317 +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.systemui.statusbar.notification.collection.render;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import android.content.Context;
-import android.testing.AndroidTestingRunner;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class ShadeViewDifferTest extends SysuiTestCase {
- private ShadeViewDiffer mDiffer;
-
- private FakeController mRootController = new FakeController(mContext, "RootController");
- private FakeController mController1 = new FakeController(mContext, "Controller1");
- private FakeController mController2 = new FakeController(mContext, "Controller2");
- private FakeController mController3 = new FakeController(mContext, "Controller3");
- private FakeController mController4 = new FakeController(mContext, "Controller4");
- private FakeController mController5 = new FakeController(mContext, "Controller5");
- private FakeController mController6 = new FakeController(mContext, "Controller6");
- private FakeController mController7 = new FakeController(mContext, "Controller7");
-
- @Mock
- ShadeViewDifferLogger mLogger;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mDiffer = new ShadeViewDiffer(mRootController, mLogger);
- }
-
- @Test
- public void testAddInitialViews() {
- // WHEN a spec is applied to an empty root
- // THEN the final tree matches the spec
- applySpecAndCheck(
- node(mController1),
- node(mController2,
- node(mController3),
- node(mController4)
- ),
- node(mController5)
- );
- }
-
- @Test
- public void testDetachViews() {
- // GIVEN a preexisting tree of controllers
- applySpecAndCheck(
- node(mController1),
- node(mController2,
- node(mController3),
- node(mController4)
- ),
- node(mController5)
- );
-
- // WHEN the new spec removes nodes
- // THEN the final tree matches the spec
- applySpecAndCheck(
- node(mController5)
- );
- }
-
- @Test
- public void testReparentChildren() {
- // GIVEN a preexisting tree of controllers
- applySpecAndCheck(
- node(mController1),
- node(mController2,
- node(mController3),
- node(mController4)
- ),
- node(mController5)
- );
-
- // WHEN the parents of the controllers are all shuffled around
- // THEN the final tree matches the spec
- applySpecAndCheck(
- node(mController1),
- node(mController4),
- node(mController3,
- node(mController2)
- )
- );
- }
-
- @Test
- public void testReorderChildren() {
- // GIVEN a preexisting tree of controllers
- applySpecAndCheck(
- node(mController1),
- node(mController2),
- node(mController3),
- node(mController4)
- );
-
- // WHEN the children change order
- // THEN the final tree matches the spec
- applySpecAndCheck(
- node(mController3),
- node(mController2),
- node(mController4),
- node(mController1)
- );
- }
-
- @Test
- public void testRemovedGroupsAreBrokenApart() {
- // GIVEN a preexisting tree with a group
- applySpecAndCheck(
- node(mController1),
- node(mController2,
- node(mController3),
- node(mController4),
- node(mController5)
- )
- );
-
- // WHEN the new spec removes the entire group
- applySpecAndCheck(
- node(mController1)
- );
-
- // THEN the group children are no longer attached to their parent
- assertNull(mController3.getView().getParent());
- assertNull(mController4.getView().getParent());
- assertNull(mController5.getView().getParent());
- }
-
- @Test
- public void testUnmanagedViews() {
- // GIVEN a preexisting tree of controllers
- applySpecAndCheck(
- node(mController1),
- node(mController2,
- node(mController3),
- node(mController4)
- ),
- node(mController5)
- );
-
- // GIVEN some additional unmanaged views attached to the tree
- View unmanagedView1 = new View(mContext);
- View unmanagedView2 = new View(mContext);
-
- mRootController.getView().addView(unmanagedView1, 1);
- mController2.getView().addView(unmanagedView2, 0);
-
- // WHEN a new spec is applied with additional nodes
- // THEN the final tree matches the spec
- applySpecAndCheck(
- node(mController1),
- node(mController2,
- node(mController3),
- node(mController4),
- node(mController6)
- ),
- node(mController5),
- node(mController7)
- );
-
- // THEN the unmanaged views have been pushed to the end of their parents
- assertEquals(unmanagedView1, mRootController.view.getChildAt(4));
- assertEquals(unmanagedView2, mController2.view.getChildAt(3));
- }
-
- private void applySpecAndCheck(NodeSpec spec) {
- mDiffer.applySpec(spec);
- checkMatchesSpec(spec);
- }
-
- private void applySpecAndCheck(SpecBuilder... children) {
- applySpecAndCheck(node(mRootController, children).build());
- }
-
- private void checkMatchesSpec(NodeSpec spec) {
- final NodeController parent = spec.getController();
- final List<NodeSpec> children = spec.getChildren();
-
- for (int i = 0; i < children.size(); i++) {
- NodeSpec childSpec = children.get(i);
- View view = parent.getChildAt(i);
-
- assertEquals(
- "Child " + i + " of parent " + parent.getNodeLabel() + " should be "
- + childSpec.getController().getNodeLabel() + " but is instead "
- + (view != null ? mDiffer.getViewLabel(view) : "null"),
- view,
- childSpec.getController().getView());
-
- if (!childSpec.getChildren().isEmpty()) {
- checkMatchesSpec(childSpec);
- }
- }
- }
-
- private static class FakeController implements NodeController {
-
- public final FrameLayout view;
- private final String mLabel;
-
- FakeController(Context context, String label) {
- view = new FrameLayout(context);
- mLabel = label;
- }
-
- @NonNull
- @Override
- public String getNodeLabel() {
- return mLabel;
- }
-
- @NonNull
- @Override
- public FrameLayout getView() {
- return view;
- }
-
- @Override
- public int getChildCount() {
- return view.getChildCount();
- }
-
- @Override
- public View getChildAt(int index) {
- return view.getChildAt(index);
- }
-
- @Override
- public void addChildAt(@NonNull NodeController child, int index) {
- view.addView(child.getView(), index);
- }
-
- @Override
- public void moveChildTo(@NonNull NodeController child, int index) {
- view.removeView(child.getView());
- view.addView(child.getView(), index);
- }
-
- @Override
- public void removeChild(@NonNull NodeController child, boolean isTransfer) {
- view.removeView(child.getView());
- }
-
- @Override
- public void onViewAdded() {
- }
-
- @Override
- public void onViewMoved() {
- }
-
- @Override
- public void onViewRemoved() {
- }
- }
-
- private static class SpecBuilder {
- private final NodeController mController;
- private final SpecBuilder[] mChildren;
-
- SpecBuilder(NodeController controller, SpecBuilder... children) {
- mController = controller;
- mChildren = children;
- }
-
- public NodeSpec build() {
- return build(null);
- }
-
- public NodeSpec build(@Nullable NodeSpec parent) {
- final NodeSpecImpl spec = new NodeSpecImpl(parent, mController);
- for (SpecBuilder childBuilder : mChildren) {
- spec.getChildren().add(childBuilder.build(spec));
- }
- return spec;
- }
- }
-
- private static SpecBuilder node(NodeController controller, SpecBuilder... children) {
- return new SpecBuilder(controller, children);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
new file mode 100644
index 000000000000..15cf17dbcf86
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
@@ -0,0 +1,234 @@
+/*
+ * 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.systemui.statusbar.notification.collection.render
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ShadeViewDifferTest : SysuiTestCase() {
+ private lateinit var differ: ShadeViewDiffer
+ private val rootController = FakeController(mContext, "RootController")
+ private val controller1 = FakeController(mContext, "Controller1")
+ private val controller2 = FakeController(mContext, "Controller2")
+ private val controller3 = FakeController(mContext, "Controller3")
+ private val controller4 = FakeController(mContext, "Controller4")
+ private val controller5 = FakeController(mContext, "Controller5")
+ private val controller6 = FakeController(mContext, "Controller6")
+ private val controller7 = FakeController(mContext, "Controller7")
+ private val logger: ShadeViewDifferLogger = mock()
+
+ @Before
+ fun setUp() {
+ differ = ShadeViewDiffer(rootController, logger)
+ }
+
+ @Test
+ fun testAddInitialViews() {
+ // WHEN a spec is applied to an empty root
+ // THEN the final tree matches the spec
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2, node(controller3), node(controller4)),
+ node(controller5)
+ )
+ }
+
+ @Test
+ fun testDetachViews() {
+ // GIVEN a preexisting tree of controllers
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2, node(controller3), node(controller4)),
+ node(controller5)
+ )
+
+ // WHEN the new spec removes nodes
+ // THEN the final tree matches the spec
+ applySpecAndCheck(node(controller5))
+ }
+
+ @Test
+ fun testReparentChildren() {
+ // GIVEN a preexisting tree of controllers
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2, node(controller3), node(controller4)),
+ node(controller5)
+ )
+
+ // WHEN the parents of the controllers are all shuffled around
+ // THEN the final tree matches the spec
+ applySpecAndCheck(
+ node(controller1),
+ node(controller4),
+ node(controller3, node(controller2))
+ )
+ }
+
+ @Test
+ fun testReorderChildren() {
+ // GIVEN a preexisting tree of controllers
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2),
+ node(controller3),
+ node(controller4)
+ )
+
+ // WHEN the children change order
+ // THEN the final tree matches the spec
+ applySpecAndCheck(
+ node(controller3),
+ node(controller2),
+ node(controller4),
+ node(controller1)
+ )
+ }
+
+ @Test
+ fun testRemovedGroupsAreBrokenApart() {
+ // GIVEN a preexisting tree with a group
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2, node(controller3), node(controller4), node(controller5))
+ )
+
+ // WHEN the new spec removes the entire group
+ applySpecAndCheck(node(controller1))
+
+ // THEN the group children are no longer attached to their parent
+ Assert.assertNull(controller3.view.parent)
+ Assert.assertNull(controller4.view.parent)
+ Assert.assertNull(controller5.view.parent)
+ }
+
+ @Test
+ fun testUnmanagedViews() {
+ // GIVEN a preexisting tree of controllers
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2, node(controller3), node(controller4)),
+ node(controller5)
+ )
+
+ // GIVEN some additional unmanaged views attached to the tree
+ val unmanagedView1 = View(mContext)
+ val unmanagedView2 = View(mContext)
+ rootController.view.addView(unmanagedView1, 1)
+ controller2.view.addView(unmanagedView2, 0)
+
+ // WHEN a new spec is applied with additional nodes
+ // THEN the final tree matches the spec
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2, node(controller3), node(controller4), node(controller6)),
+ node(controller5),
+ node(controller7)
+ )
+
+ // THEN the unmanaged views have been pushed to the end of their parents
+ Assert.assertEquals(unmanagedView1, rootController.view.getChildAt(4))
+ Assert.assertEquals(unmanagedView2, controller2.view.getChildAt(3))
+ }
+
+ private fun applySpecAndCheck(spec: NodeSpec) {
+ differ.applySpec(spec)
+ checkMatchesSpec(spec)
+ }
+
+ private fun applySpecAndCheck(vararg children: SpecBuilder) {
+ applySpecAndCheck(node(rootController, *children).build())
+ }
+
+ private fun checkMatchesSpec(spec: NodeSpec) {
+ val parent = spec.controller
+ val children = spec.children
+ for (i in children.indices) {
+ val childSpec = children[i]
+ val view = parent.getChildAt(i)
+ Assert.assertEquals(
+ "Child $i of parent ${parent.nodeLabel} " +
+ "should be ${childSpec.controller.nodeLabel} " +
+ "but instead " +
+ view?.let(differ::getViewLabel),
+ view,
+ childSpec.controller.view
+ )
+ if (childSpec.children.isNotEmpty()) {
+ checkMatchesSpec(childSpec)
+ }
+ }
+ }
+
+ private class FakeController(context: Context, label: String) : NodeController {
+ override val view: FrameLayout = FrameLayout(context)
+ override val nodeLabel: String = label
+ override fun getChildCount(): Int = view.childCount
+
+ override fun getChildAt(index: Int): View? {
+ return view.getChildAt(index)
+ }
+
+ override fun addChildAt(child: NodeController, index: Int) {
+ view.addView(child.view, index)
+ }
+
+ override fun moveChildTo(child: NodeController, index: Int) {
+ view.removeView(child.view)
+ view.addView(child.view, index)
+ }
+
+ override fun removeChild(child: NodeController, isTransfer: Boolean) {
+ view.removeView(child.view)
+ }
+
+ override fun onViewAdded() {}
+ override fun onViewMoved() {}
+ override fun onViewRemoved() {}
+ }
+
+ private class SpecBuilder(
+ private val mController: NodeController,
+ private val children: Array<out SpecBuilder>
+ ) {
+
+ @JvmOverloads
+ fun build(parent: NodeSpec? = null): NodeSpec {
+ val spec = NodeSpecImpl(parent, mController)
+ for (childBuilder in children) {
+ spec.children.add(childBuilder.build(spec))
+ }
+ return spec
+ }
+ }
+
+ companion object {
+ private fun node(controller: NodeController, vararg children: SpecBuilder): SpecBuilder {
+ return SpecBuilder(controller, children)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
index 120bf791c462..e49652148f3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -219,6 +219,7 @@ class GuestUserInteractorTest : SysuiTestCase() {
repository.setUserInfos(listOf(NON_GUEST_USER_INFO, EPHEMERAL_GUEST_USER_INFO))
repository.setSelectedUserInfo(EPHEMERAL_GUEST_USER_INFO)
val targetUserId = NON_GUEST_USER_INFO.id
+ val ephemeralGuestUserHandle = UserHandle.of(EPHEMERAL_GUEST_USER_INFO.id)
underTest.exit(
guestUserId = GUEST_USER_INFO.id,
@@ -230,7 +231,7 @@ class GuestUserInteractorTest : SysuiTestCase() {
)
verify(manager).markGuestForDeletion(EPHEMERAL_GUEST_USER_INFO.id)
- verify(manager).removeUser(EPHEMERAL_GUEST_USER_INFO.id)
+ verify(manager).removeUserWhenPossible(ephemeralGuestUserHandle, false)
verify(switchUser).invoke(targetUserId)
}
@@ -240,6 +241,7 @@ class GuestUserInteractorTest : SysuiTestCase() {
whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
repository.setSelectedUserInfo(GUEST_USER_INFO)
val targetUserId = NON_GUEST_USER_INFO.id
+ val guestUserHandle = UserHandle.of(GUEST_USER_INFO.id)
underTest.exit(
guestUserId = GUEST_USER_INFO.id,
@@ -251,7 +253,7 @@ class GuestUserInteractorTest : SysuiTestCase() {
)
verify(manager).markGuestForDeletion(GUEST_USER_INFO.id)
- verify(manager).removeUser(GUEST_USER_INFO.id)
+ verify(manager).removeUserWhenPossible(guestUserHandle, false)
verify(switchUser).invoke(targetUserId)
}
@@ -296,6 +298,7 @@ class GuestUserInteractorTest : SysuiTestCase() {
repository.setSelectedUserInfo(GUEST_USER_INFO)
val targetUserId = NON_GUEST_USER_INFO.id
+ val guestUserHandle = UserHandle.of(GUEST_USER_INFO.id)
underTest.remove(
guestUserId = GUEST_USER_INFO.id,
targetUserId = targetUserId,
@@ -305,7 +308,7 @@ class GuestUserInteractorTest : SysuiTestCase() {
)
verify(manager).markGuestForDeletion(GUEST_USER_INFO.id)
- verify(manager).removeUser(GUEST_USER_INFO.id)
+ verify(manager).removeUserWhenPossible(guestUserHandle, false)
verify(switchUser).invoke(targetUserId)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index c2543589bfb8..379bb28ae032 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -26,8 +26,8 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -44,6 +44,7 @@ import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
@@ -135,9 +136,10 @@ public class ImageWallpaperTest extends SysuiTestCase {
when(mWallpaperBitmap.getHeight()).thenReturn(mBitmapHeight);
// set up wallpaper manager
- when(mWallpaperManager.peekBitmapDimensions()).thenReturn(
- new Rect(0, 0, mBitmapWidth, mBitmapHeight));
- when(mWallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
+ when(mWallpaperManager.peekBitmapDimensions())
+ .thenReturn(new Rect(0, 0, mBitmapWidth, mBitmapHeight));
+ when(mWallpaperManager.getBitmapAsUser(eq(UserHandle.USER_CURRENT), anyBoolean()))
+ .thenReturn(mWallpaperBitmap);
when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
// set up surface
@@ -286,9 +288,6 @@ public class ImageWallpaperTest extends SysuiTestCase {
testMinSurfaceHelper(8, 8);
testMinSurfaceHelper(100, 2000);
testMinSurfaceHelper(200, 1);
- testMinSurfaceHelper(0, 1);
- testMinSurfaceHelper(1, 0);
- testMinSurfaceHelper(0, 0);
}
private void testMinSurfaceHelper(int bitmapWidth, int bitmapHeight) {
@@ -307,28 +306,6 @@ public class ImageWallpaperTest extends SysuiTestCase {
}
@Test
- public void testZeroBitmap() {
- // test that a frame is never drawn with a 0 bitmap
- testZeroBitmapHelper(0, 1);
- testZeroBitmapHelper(1, 0);
- testZeroBitmapHelper(0, 0);
- }
-
- private void testZeroBitmapHelper(int bitmapWidth, int bitmapHeight) {
-
- clearInvocations(mSurfaceHolder);
- setBitmapDimensions(bitmapWidth, bitmapHeight);
-
- ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
- ImageWallpaper.CanvasEngine engine =
- (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
- ImageWallpaper.CanvasEngine spyEngine = spy(engine);
- spyEngine.onCreate(mSurfaceHolder);
- spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
- verify(spyEngine, never()).drawFrameOnCanvas(any());
- }
-
- @Test
public void testLoadDrawAndUnloadBitmap() {
setBitmapDimensions(LOW_BMP_WIDTH, LOW_BMP_HEIGHT);
diff --git a/services/core/java/com/android/server/ServiceThread.java b/services/core/java/com/android/server/ServiceThread.java
index 6d8e49c7c869..3ea4b86d5296 100644
--- a/services/core/java/com/android/server/ServiceThread.java
+++ b/services/core/java/com/android/server/ServiceThread.java
@@ -22,6 +22,8 @@ import android.os.Looper;
import android.os.Process;
import android.os.StrictMode;
+import com.android.server.am.BroadcastLoopers;
+
/**
* Special handler thread that we create for system services that require their own loopers.
*/
@@ -46,6 +48,14 @@ public class ServiceThread extends HandlerThread {
super.run();
}
+ @Override
+ protected void onLooperPrepared() {
+ // Almost all service threads are used for dispatching broadcast
+ // intents, so register ourselves to ensure that "wait-for-broadcast"
+ // shell commands are able to drain any pending broadcasts
+ BroadcastLoopers.addLooper(getLooper());
+ }
+
protected static Handler makeSharedHandler(Looper looper) {
return new Handler(looper, /*callback=*/ null, /* async=*/ false, /* shared=*/ true);
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 72876f669f01..c280719b06a3 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -147,6 +147,7 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.am.BroadcastLoopers;
import com.android.server.pm.Installer;
import com.android.server.pm.UserManagerInternal;
import com.android.server.storage.AppFuseBridge;
@@ -1809,6 +1810,7 @@ class StorageManagerService extends IStorageManager.Stub
HandlerThread hthread = new HandlerThread(TAG);
hthread.start();
+ BroadcastLoopers.addLooper(hthread.getLooper());
mHandler = new StorageManagerServiceHandler(hthread.getLooper());
// Add OBB Action Handler to StorageManagerService thread.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ea6688442fa4..c95495747ead 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -470,6 +470,7 @@ import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
public class ActivityManagerService extends IActivityManager.Stub
@@ -2508,6 +2509,8 @@ public class ActivityManagerService extends IActivityManager.Stub
Watchdog.getInstance().addMonitor(this);
Watchdog.getInstance().addThread(mHandler);
+ BroadcastLoopers.addLooper(BackgroundThread.getHandler().getLooper());
+
// bind background threads to little cores
// this is expected to fail inside of framework tests because apps can't touch cpusets directly
// make sure we've already adjusted system_server's internal view of itself first
@@ -3455,13 +3458,13 @@ public class ActivityManagerService extends IActivityManager.Stub
}
/**
- * @param firstPidOffsets Optional, when it's set, it receives the start/end offset
+ * @param firstPidEndOffset Optional, when it's set, it receives the start/end offset
* of the very first pid to be dumped.
*/
/* package */ static File dumpStackTraces(ArrayList<Integer> firstPids,
ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
- long[] firstPidOffsets, String subject, String criticalEventSection,
+ AtomicLong firstPidEndOffset, String subject, String criticalEventSection,
AnrLatencyTracker latencyTracker) {
try {
if (latencyTracker != null) {
@@ -3534,15 +3537,10 @@ public class ActivityManagerService extends IActivityManager.Stub
+ (criticalEventSection != null ? criticalEventSection : ""));
}
- Pair<Long, Long> offsets = dumpStackTraces(
+ long firstPidEndPos = dumpStackTraces(
tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids, latencyTracker);
- if (firstPidOffsets != null) {
- if (offsets == null) {
- firstPidOffsets[0] = firstPidOffsets[1] = -1;
- } else {
- firstPidOffsets[0] = offsets.first; // Start offset to the ANR trace file
- firstPidOffsets[1] = offsets.second; // End offset to the ANR trace file
- }
+ if (firstPidEndOffset != null) {
+ firstPidEndOffset.set(firstPidEndPos);
}
return tracesFile;
@@ -3661,9 +3659,9 @@ public class ActivityManagerService extends IActivityManager.Stub
/**
- * @return The start/end offset of the trace of the very first PID
+ * @return The end offset of the trace of the very first PID
*/
- public static Pair<Long, Long> dumpStackTraces(String tracesFile,
+ public static long dumpStackTraces(String tracesFile,
ArrayList<Integer> firstPids, ArrayList<Integer> nativePids,
ArrayList<Integer> extraPids, AnrLatencyTracker latencyTracker) {
@@ -3679,7 +3677,6 @@ public class ActivityManagerService extends IActivityManager.Stub
// As applications are usually interested with the ANR stack traces, but we can't share with
// them the stack traces other than their own stacks. So after the very first PID is
// dumped, remember the current file size.
- long firstPidStart = -1;
long firstPidEnd = -1;
// First collect all of the stacks of the most important pids.
@@ -3692,11 +3689,6 @@ public class ActivityManagerService extends IActivityManager.Stub
final int pid = firstPids.get(i);
// We don't copy ANR traces from the system_server intentionally.
final boolean firstPid = i == 0 && MY_PID != pid;
- File tf = null;
- if (firstPid) {
- tf = new File(tracesFile);
- firstPidStart = tf.exists() ? tf.length() : 0;
- }
if (latencyTracker != null) {
latencyTracker.dumpingPidStarted(pid);
}
@@ -3712,11 +3704,11 @@ public class ActivityManagerService extends IActivityManager.Stub
if (remainingTime <= 0) {
Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + pid
+ "); deadline exceeded.");
- return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
+ return firstPidEnd;
}
if (firstPid) {
- firstPidEnd = tf.length();
+ firstPidEnd = new File(tracesFile).length();
// Full latency dump
if (latencyTracker != null) {
appendtoANRFile(tracesFile,
@@ -3755,7 +3747,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (remainingTime <= 0) {
Slog.e(TAG, "Aborting stack trace dump (current native pid=" + pid +
"); deadline exceeded.");
- return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
+ return firstPidEnd;
}
if (DEBUG_ANR) {
@@ -3785,7 +3777,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (remainingTime <= 0) {
Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + pid +
"); deadline exceeded.");
- return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
+ return firstPidEnd;
}
if (DEBUG_ANR) {
@@ -3800,7 +3792,7 @@ public class ActivityManagerService extends IActivityManager.Stub
appendtoANRFile(tracesFile, "----- dumping ended at " + SystemClock.uptimeMillis() + "\n");
Slog.i(TAG, "Done dumping");
- return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
+ return firstPidEnd;
}
@Override
diff --git a/services/core/java/com/android/server/am/BroadcastLoopers.java b/services/core/java/com/android/server/am/BroadcastLoopers.java
index bebb48473fc3..b828720c9162 100644
--- a/services/core/java/com/android/server/am/BroadcastLoopers.java
+++ b/services/core/java/com/android/server/am/BroadcastLoopers.java
@@ -25,6 +25,8 @@ import android.os.SystemClock;
import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
@@ -37,6 +39,7 @@ import java.util.concurrent.CountDownLatch;
public class BroadcastLoopers {
private static final String TAG = "BroadcastLoopers";
+ @GuardedBy("sLoopers")
private static final ArraySet<Looper> sLoopers = new ArraySet<>();
/**
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 9e9eb71db4e5..95cefea532a6 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -144,14 +144,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
mRunning = new BroadcastProcessQueue[mConstants.MAX_RUNNING_PROCESS_QUEUES];
}
- // TODO: add support for replacing pending broadcasts
- // TODO: add support for merging pending broadcasts
-
- // TODO: consider reordering foreground broadcasts within queue
-
- // TODO: pause queues when background services are running
- // TODO: pause queues when processes are frozen
-
/**
* Map from UID to per-process broadcast queues. If a UID hosts more than
* one process, each additional process is stored as a linked list using
@@ -412,21 +404,24 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
queue.runningTraceTrackName = TAG + ".mRunning[" + queueIndex + "]";
queue.runningOomAdjusted = queue.isPendingManifest();
+ // If already warm, we can make OOM adjust request immediately;
+ // otherwise we need to wait until process becomes warm
+ if (processWarm) {
+ notifyStartedRunning(queue);
+ updateOomAdj |= queue.runningOomAdjusted;
+ }
+
// If we're already warm, schedule next pending broadcast now;
// otherwise we'll wait for the cold start to circle back around
queue.makeActiveNextPending();
if (processWarm) {
queue.traceProcessRunningBegin();
- notifyStartedRunning(queue);
scheduleReceiverWarmLocked(queue);
} else {
queue.traceProcessStartingBegin();
scheduleReceiverColdLocked(queue);
}
- // Only kick off an OOM adjustment pass if needed
- updateOomAdj |= queue.runningOomAdjusted;
-
// Move to considering next runnable queue
queue = nextQueue;
}
@@ -464,9 +459,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// now; dispatch its next broadcast and clear the slot
mRunningColdStart = null;
+ // Now that we're running warm, we can finally request that OOM
+ // adjust we've been waiting for
+ notifyStartedRunning(queue);
+ mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
+
queue.traceProcessEnd();
queue.traceProcessRunningBegin();
- notifyStartedRunning(queue);
scheduleReceiverWarmLocked(queue);
// We might be willing to kick off another cold start
@@ -509,7 +508,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
if (queue != null) {
// If queue was running a broadcast, fail it
if (queue.isActive()) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
+ "onApplicationCleanupLocked");
}
// Skip any pending registered receivers, since the old process
@@ -661,7 +661,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// Ignore registered receivers from a previous PID
if (receiver instanceof BroadcastFilter) {
mRunningColdStart = null;
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED,
+ "BroadcastFilter for cold app");
return;
}
@@ -683,7 +684,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
hostingRecord, zygotePolicyFlags, allowWhileBooting, false);
if (queue.app == null) {
mRunningColdStart = null;
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
+ "startProcessLocked failed");
return;
}
}
@@ -714,33 +716,37 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// If someone already finished this broadcast, finish immediately
final int oldDeliveryState = getDeliveryState(r, index);
if (isDeliveryStateTerminal(oldDeliveryState)) {
- finishReceiverLocked(queue, oldDeliveryState);
+ finishReceiverLocked(queue, oldDeliveryState, "already terminal state");
return;
}
// Consider additional cases where we'd want to finish immediately
if (app.isInFullBackup()) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
return;
}
if (mSkipPolicy.shouldSkip(r, receiver)) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy");
return;
}
final Intent receiverIntent = r.getReceiverIntent(receiver);
if (receiverIntent == null) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
return;
}
// Ignore registered receivers from a previous PID
if ((receiver instanceof BroadcastFilter)
&& ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED,
+ "BroadcastFilter for mismatched PID");
return;
}
- if (mService.mProcessesReady && !r.timeoutExempt) {
+ // Skip ANR tracking early during boot, when requested, or when we
+ // immediately assume delivery success
+ final boolean assumeDelivered = (receiver instanceof BroadcastFilter) && !r.ordered;
+ if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
@@ -768,7 +774,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
- setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
+ setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED,
+ "scheduleReceiverWarmLocked");
final IApplicationThread thread = app.getOnewayThread();
if (thread != null) {
@@ -782,8 +789,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// TODO: consider making registered receivers of unordered
// broadcasts report results to detect ANRs
- if (!r.ordered) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
+ if (assumeDelivered) {
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED,
+ "assuming delivered");
}
} else {
notifyScheduleReceiver(app, r, (ResolveInfo) receiver);
@@ -797,10 +805,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
logw(msg);
app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
app.setKilled(true);
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app");
}
} else {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
+ "missing IApplicationThread");
}
}
@@ -809,9 +818,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
* ordered broadcast; assumes the sender is still a warm process.
*/
private void scheduleResultTo(@NonNull BroadcastRecord r) {
- if ((r.resultToApp == null) || (r.resultTo == null)) return;
+ if (r.resultTo == null) return;
final ProcessRecord app = r.resultToApp;
- final IApplicationThread thread = app.getOnewayThread();
+ final IApplicationThread thread = (app != null) ? app.getOnewayThread() : null;
if (thread != null) {
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
app, OOM_ADJ_REASON_FINISH_RECEIVER);
@@ -844,7 +853,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
- finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT);
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
+ "deliveryTimeoutHardLocked");
}
@Override
@@ -871,16 +881,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
if (r.resultAbort) {
for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) {
setDeliveryState(null, null, r, i, r.receivers.get(i),
- BroadcastRecord.DELIVERY_SKIPPED);
+ BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
}
}
}
- return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
+ return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
}
private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
- @DeliveryState int deliveryState) {
+ @DeliveryState int deliveryState, @NonNull String reason) {
checkState(queue.isActive(), "isActive");
final ProcessRecord app = queue.app;
@@ -888,7 +898,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
final int index = queue.getActiveIndex();
final Object receiver = r.receivers.get(index);
- setDeliveryState(queue, app, r, index, receiver, deliveryState);
+ setDeliveryState(queue, app, r, index, receiver, deliveryState, reason);
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
r.anrCount++;
@@ -935,7 +945,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
*/
private void setDeliveryState(@Nullable BroadcastProcessQueue queue,
@Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index,
- @NonNull Object receiver, @DeliveryState int newDeliveryState) {
+ @NonNull Object receiver, @DeliveryState int newDeliveryState, String reason) {
final int oldDeliveryState = getDeliveryState(r, index);
// Only apply state when we haven't already reached a terminal state;
@@ -963,7 +973,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
logw("Delivery state of " + r + " to " + receiver
+ " via " + app + " changed from "
+ deliveryStateToString(oldDeliveryState) + " to "
- + deliveryStateToString(newDeliveryState));
+ + deliveryStateToString(newDeliveryState) + " because " + reason);
}
r.terminalCount++;
@@ -1053,7 +1063,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
* of it matching a predicate.
*/
private final BroadcastConsumer mBroadcastConsumerSkip = (r, i) -> {
- setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+ setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED,
+ "mBroadcastConsumerSkip");
};
/**
@@ -1061,7 +1072,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
* cancelled, usually as a result of it matching a predicate.
*/
private final BroadcastConsumer mBroadcastConsumerSkipAndCanceled = (r, i) -> {
- setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+ setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED,
+ "mBroadcastConsumerSkipAndCanceled");
r.resultCode = Activity.RESULT_CANCELED;
r.resultData = null;
r.resultExtras = null;
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index 60fddf0c7f22..481ab17b609e 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -21,6 +21,7 @@ import static com.android.server.am.ActivityManagerService.checkComponentPermiss
import static com.android.server.am.BroadcastQueue.TAG;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -42,6 +43,8 @@ import android.util.Slog;
import com.android.internal.util.ArrayUtils;
+import java.util.Objects;
+
/**
* Policy logic that decides if delivery of a particular {@link BroadcastRecord}
* should be skipped for a given {@link ResolveInfo} or {@link BroadcastFilter}.
@@ -51,8 +54,8 @@ import com.android.internal.util.ArrayUtils;
public class BroadcastSkipPolicy {
private final ActivityManagerService mService;
- public BroadcastSkipPolicy(ActivityManagerService service) {
- mService = service;
+ public BroadcastSkipPolicy(@NonNull ActivityManagerService service) {
+ mService = Objects.requireNonNull(service);
}
/**
@@ -60,18 +63,39 @@ public class BroadcastSkipPolicy {
* the given {@link BroadcastFilter} or {@link ResolveInfo}.
*/
public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull Object target) {
+ final String msg = shouldSkipMessage(r, target);
+ if (msg != null) {
+ Slog.w(TAG, msg);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if the given {@link BroadcastRecord} is eligible to be sent to
+ * the given {@link BroadcastFilter} or {@link ResolveInfo}.
+ *
+ * @return message indicating why the argument should be skipped, otherwise
+ * {@code null} if it can proceed.
+ */
+ public @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r, @NonNull Object target) {
if (target instanceof BroadcastFilter) {
- return shouldSkip(r, (BroadcastFilter) target);
+ return shouldSkipMessage(r, (BroadcastFilter) target);
} else {
- return shouldSkip(r, (ResolveInfo) target);
+ return shouldSkipMessage(r, (ResolveInfo) target);
}
}
/**
* Determine if the given {@link BroadcastRecord} is eligible to be sent to
* the given {@link ResolveInfo}.
+ *
+ * @return message indicating why the argument should be skipped, otherwise
+ * {@code null} if it can proceed.
*/
- public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull ResolveInfo info) {
+ private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r,
+ @NonNull ResolveInfo info) {
final BroadcastOptions brOptions = r.options;
final ComponentName component = new ComponentName(
info.activityInfo.applicationInfo.packageName,
@@ -82,58 +106,52 @@ public class BroadcastSkipPolicy {
< brOptions.getMinManifestReceiverApiLevel() ||
info.activityInfo.applicationInfo.targetSdkVersion
> brOptions.getMaxManifestReceiverApiLevel())) {
- Slog.w(TAG, "Target SDK mismatch: receiver " + info.activityInfo
+ return "Target SDK mismatch: receiver " + info.activityInfo
+ " targets " + info.activityInfo.applicationInfo.targetSdkVersion
+ " but delivery restricted to ["
+ brOptions.getMinManifestReceiverApiLevel() + ", "
+ brOptions.getMaxManifestReceiverApiLevel()
- + "] broadcasting " + broadcastDescription(r, component));
- return true;
+ + "] broadcasting " + broadcastDescription(r, component);
}
if (brOptions != null &&
!brOptions.testRequireCompatChange(info.activityInfo.applicationInfo.uid)) {
- Slog.w(TAG, "Compat change filtered: broadcasting " + broadcastDescription(r, component)
+ return "Compat change filtered: broadcasting " + broadcastDescription(r, component)
+ " to uid " + info.activityInfo.applicationInfo.uid + " due to compat change "
- + r.options.getRequireCompatChangeId());
- return true;
+ + r.options.getRequireCompatChangeId();
}
if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
- Slog.w(TAG, "Association not allowed: broadcasting "
- + broadcastDescription(r, component));
- return true;
+ return "Association not allowed: broadcasting "
+ + broadcastDescription(r, component);
}
if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid)) {
- Slog.w(TAG, "Firewall blocked: broadcasting "
- + broadcastDescription(r, component));
- return true;
+ return "Firewall blocked: broadcasting "
+ + broadcastDescription(r, component);
}
int perm = checkComponentPermission(info.activityInfo.permission,
r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
info.activityInfo.exported);
if (perm != PackageManager.PERMISSION_GRANTED) {
if (!info.activityInfo.exported) {
- Slog.w(TAG, "Permission Denial: broadcasting "
+ return "Permission Denial: broadcasting "
+ broadcastDescription(r, component)
- + " is not exported from uid " + info.activityInfo.applicationInfo.uid);
+ + " is not exported from uid " + info.activityInfo.applicationInfo.uid;
} else {
- Slog.w(TAG, "Permission Denial: broadcasting "
+ return "Permission Denial: broadcasting "
+ broadcastDescription(r, component)
- + " requires " + info.activityInfo.permission);
+ + " requires " + info.activityInfo.permission;
}
- return true;
} else if (info.activityInfo.permission != null) {
final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission);
if (opCode != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(opCode,
r.callingUid, r.callerPackage, r.callerFeatureId,
"Broadcast delivered to " + info.activityInfo.name)
!= AppOpsManager.MODE_ALLOWED) {
- Slog.w(TAG, "Appop Denial: broadcasting "
+ return "Appop Denial: broadcasting "
+ broadcastDescription(r, component)
+ " requires appop " + AppOpsManager.permissionToOp(
- info.activityInfo.permission));
- return true;
+ info.activityInfo.permission);
}
}
@@ -142,38 +160,34 @@ public class BroadcastSkipPolicy {
android.Manifest.permission.INTERACT_ACROSS_USERS,
info.activityInfo.applicationInfo.uid)
!= PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Permission Denial: Receiver " + component.flattenToShortString()
+ return "Permission Denial: Receiver " + component.flattenToShortString()
+ " requests FLAG_SINGLE_USER, but app does not hold "
- + android.Manifest.permission.INTERACT_ACROSS_USERS);
- return true;
+ + android.Manifest.permission.INTERACT_ACROSS_USERS;
}
}
if (info.activityInfo.applicationInfo.isInstantApp()
&& r.callingUid != info.activityInfo.applicationInfo.uid) {
- Slog.w(TAG, "Instant App Denial: receiving "
+ return "Instant App Denial: receiving "
+ r.intent
+ " to " + component.flattenToShortString()
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")"
- + " Instant Apps do not support manifest receivers");
- return true;
+ + " Instant Apps do not support manifest receivers";
}
if (r.callerInstantApp
&& (info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0
&& r.callingUid != info.activityInfo.applicationInfo.uid) {
- Slog.w(TAG, "Instant App Denial: receiving "
+ return "Instant App Denial: receiving "
+ r.intent
+ " to " + component.flattenToShortString()
+ " requires receiver have visibleToInstantApps set"
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
if (r.curApp != null && r.curApp.mErrorState.isCrashing()) {
// If the target process is crashing, just skip it.
- Slog.w(TAG, "Skipping deliver ordered [" + r.queue.toString() + "] " + r
- + " to " + r.curApp + ": process crashing");
- return true;
+ return "Skipping deliver ordered [" + r.queue.toString() + "] " + r
+ + " to " + r.curApp + ": process crashing";
}
boolean isAvailable = false;
@@ -183,15 +197,13 @@ public class BroadcastSkipPolicy {
UserHandle.getUserId(info.activityInfo.applicationInfo.uid));
} catch (Exception e) {
// all such failures mean we skip this receiver
- Slog.w(TAG, "Exception getting recipient info for "
- + info.activityInfo.packageName, e);
+ return "Exception getting recipient info for "
+ + info.activityInfo.packageName;
}
if (!isAvailable) {
- Slog.w(TAG,
- "Skipping delivery to " + info.activityInfo.packageName + " / "
+ return "Skipping delivery to " + info.activityInfo.packageName + " / "
+ info.activityInfo.applicationInfo.uid
- + " : package no longer available");
- return true;
+ + " : package no longer available";
}
// If permissions need a review before any of the app components can run, we drop
@@ -201,10 +213,8 @@ public class BroadcastSkipPolicy {
if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
info.activityInfo.packageName, UserHandle.getUserId(
info.activityInfo.applicationInfo.uid))) {
- Slog.w(TAG,
- "Skipping delivery: permission review required for "
- + broadcastDescription(r, component));
- return true;
+ return "Skipping delivery: permission review required for "
+ + broadcastDescription(r, component);
}
final int allowed = mService.getAppStartModeLOSP(
@@ -216,10 +226,9 @@ public class BroadcastSkipPolicy {
// to it and the app is in a state that should not receive it
// (depending on how getAppStartModeLOSP has determined that).
if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
- Slog.w(TAG, "Background execution disabled: receiving "
+ return "Background execution disabled: receiving "
+ r.intent + " to "
- + component.flattenToShortString());
- return true;
+ + component.flattenToShortString();
} else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
|| (r.intent.getComponent() == null
&& r.intent.getPackage() == null
@@ -228,10 +237,9 @@ public class BroadcastSkipPolicy {
&& !isSignaturePerm(r.requiredPermissions))) {
mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
component.getPackageName());
- Slog.w(TAG, "Background execution not allowed: receiving "
+ return "Background execution not allowed: receiving "
+ r.intent + " to "
- + component.flattenToShortString());
- return true;
+ + component.flattenToShortString();
}
}
@@ -239,10 +247,8 @@ public class BroadcastSkipPolicy {
&& !mService.mUserController
.isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid),
0 /* flags */)) {
- Slog.w(TAG,
- "Skipping delivery to " + info.activityInfo.packageName + " / "
- + info.activityInfo.applicationInfo.uid + " : user is not running");
- return true;
+ return "Skipping delivery to " + info.activityInfo.packageName + " / "
+ + info.activityInfo.applicationInfo.uid + " : user is not running";
}
if (r.excludedPermissions != null && r.excludedPermissions.length > 0) {
@@ -268,13 +274,15 @@ public class BroadcastSkipPolicy {
info.activityInfo.applicationInfo.uid,
info.activityInfo.packageName)
== AppOpsManager.MODE_ALLOWED)) {
- return true;
+ return "Skipping delivery to " + info.activityInfo.packageName
+ + " due to excluded permission " + excludedPermission;
}
} else {
// When there is no app op associated with the permission,
// skip when permission is granted.
if (perm == PackageManager.PERMISSION_GRANTED) {
- return true;
+ return "Skipping delivery to " + info.activityInfo.packageName
+ + " due to excluded permission " + excludedPermission;
}
}
}
@@ -283,13 +291,12 @@ public class BroadcastSkipPolicy {
// Check that the receiver does *not* belong to any of the excluded packages
if (r.excludedPackages != null && r.excludedPackages.length > 0) {
if (ArrayUtils.contains(r.excludedPackages, component.getPackageName())) {
- Slog.w(TAG, "Skipping delivery of excluded package "
+ return "Skipping delivery of excluded package "
+ r.intent + " to "
+ component.flattenToShortString()
+ " excludes package " + component.getPackageName()
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
}
@@ -307,95 +314,94 @@ public class BroadcastSkipPolicy {
perm = PackageManager.PERMISSION_DENIED;
}
if (perm != PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Permission Denial: receiving "
+ return "Permission Denial: receiving "
+ r.intent + " to "
+ component.flattenToShortString()
+ " requires " + requiredPermission
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) {
if (!noteOpForManifestReceiver(appOp, r, info, component)) {
- return true;
+ return "Skipping delivery to " + info.activityInfo.packageName
+ + " due to required appop " + appOp;
}
}
}
}
if (r.appOp != AppOpsManager.OP_NONE) {
if (!noteOpForManifestReceiver(r.appOp, r, info, component)) {
- return true;
+ return "Skipping delivery to " + info.activityInfo.packageName
+ + " due to required appop " + r.appOp;
}
}
- return false;
+ return null;
}
/**
* Determine if the given {@link BroadcastRecord} is eligible to be sent to
* the given {@link BroadcastFilter}.
+ *
+ * @return message indicating why the argument should be skipped, otherwise
+ * {@code null} if it can proceed.
*/
- public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull BroadcastFilter filter) {
+ private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r,
+ @NonNull BroadcastFilter filter) {
if (r.options != null && !r.options.testRequireCompatChange(filter.owningUid)) {
- Slog.w(TAG, "Compat change filtered: broadcasting " + r.intent.toString()
+ return "Compat change filtered: broadcasting " + r.intent.toString()
+ " to uid " + filter.owningUid + " due to compat change "
- + r.options.getRequireCompatChangeId());
- return true;
+ + r.options.getRequireCompatChangeId();
}
if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
filter.packageName, filter.owningUid)) {
- Slog.w(TAG, "Association not allowed: broadcasting "
+ return "Association not allowed: broadcasting "
+ r.intent.toString()
+ " from " + r.callerPackage + " (pid=" + r.callingPid
+ ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
- + filter);
- return true;
+ + filter;
}
if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
r.callingPid, r.resolvedType, filter.receiverList.uid)) {
- Slog.w(TAG, "Firewall blocked: broadcasting "
+ return "Firewall blocked: broadcasting "
+ r.intent.toString()
+ " from " + r.callerPackage + " (pid=" + r.callingPid
+ ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
- + filter);
- return true;
+ + filter;
}
// Check that the sender has permission to send to this receiver
if (filter.requiredPermission != null) {
int perm = checkComponentPermission(filter.requiredPermission,
r.callingPid, r.callingUid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Permission Denial: broadcasting "
+ return "Permission Denial: broadcasting "
+ r.intent.toString()
+ " from " + r.callerPackage + " (pid="
+ r.callingPid + ", uid=" + r.callingUid + ")"
+ " requires " + filter.requiredPermission
- + " due to registered receiver " + filter);
- return true;
+ + " due to registered receiver " + filter;
} else {
final int opCode = AppOpsManager.permissionToOpCode(filter.requiredPermission);
if (opCode != AppOpsManager.OP_NONE
&& mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid,
r.callerPackage, r.callerFeatureId, "Broadcast sent to protected receiver")
!= AppOpsManager.MODE_ALLOWED) {
- Slog.w(TAG, "Appop Denial: broadcasting "
+ return "Appop Denial: broadcasting "
+ r.intent.toString()
+ " from " + r.callerPackage + " (pid="
+ r.callingPid + ", uid=" + r.callingUid + ")"
+ " requires appop " + AppOpsManager.permissionToOp(
filter.requiredPermission)
- + " due to registered receiver " + filter);
- return true;
+ + " due to registered receiver " + filter;
}
}
}
if ((filter.receiverList.app == null || filter.receiverList.app.isKilled()
|| filter.receiverList.app.mErrorState.isCrashing())) {
- Slog.w(TAG, "Skipping deliver [" + r.queue.toString() + "] " + r
- + " to " + filter.receiverList + ": process gone or crashing");
- return true;
+ return "Skipping deliver [" + r.queue.toString() + "] " + r
+ + " to " + filter.receiverList + ": process gone or crashing";
}
// Ensure that broadcasts are only sent to other Instant Apps if they are marked as
@@ -405,28 +411,26 @@ public class BroadcastSkipPolicy {
if (!visibleToInstantApps && filter.instantApp
&& filter.receiverList.uid != r.callingUid) {
- Slog.w(TAG, "Instant App Denial: receiving "
+ return "Instant App Denial: receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")"
- + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS");
- return true;
+ + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS";
}
if (!filter.visibleToInstantApp && r.callerInstantApp
&& filter.receiverList.uid != r.callingUid) {
- Slog.w(TAG, "Instant App Denial: receiving "
+ return "Instant App Denial: receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
+ " requires receiver be visible to instant apps"
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
// Check that the receiver has the required permission(s) to receive this broadcast.
@@ -436,15 +440,14 @@ public class BroadcastSkipPolicy {
int perm = checkComponentPermission(requiredPermission,
filter.receiverList.pid, filter.receiverList.uid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Permission Denial: receiving "
+ return "Permission Denial: receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
+ " requires " + requiredPermission
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
@@ -452,7 +455,7 @@ public class BroadcastSkipPolicy {
filter.receiverList.uid, filter.packageName, filter.featureId,
"Broadcast delivered to registered receiver " + filter.receiverId)
!= AppOpsManager.MODE_ALLOWED) {
- Slog.w(TAG, "Appop Denial: receiving "
+ return "Appop Denial: receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
@@ -460,8 +463,7 @@ public class BroadcastSkipPolicy {
+ " requires appop " + AppOpsManager.permissionToOp(
requiredPermission)
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
}
}
@@ -469,14 +471,13 @@ public class BroadcastSkipPolicy {
int perm = checkComponentPermission(null,
filter.receiverList.pid, filter.receiverList.uid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Permission Denial: security check failed when receiving "
+ return "Permission Denial: security check failed when receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
}
// Check that the receiver does *not* have any excluded permissions
@@ -496,7 +497,7 @@ public class BroadcastSkipPolicy {
filter.receiverList.uid,
filter.packageName)
== AppOpsManager.MODE_ALLOWED)) {
- Slog.w(TAG, "Appop Denial: receiving "
+ return "Appop Denial: receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
@@ -504,22 +505,20 @@ public class BroadcastSkipPolicy {
+ " excludes appop " + AppOpsManager.permissionToOp(
excludedPermission)
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
} else {
// When there is no app op associated with the permission,
// skip when permission is granted.
if (perm == PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Permission Denial: receiving "
+ return "Permission Denial: receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
+ " excludes " + excludedPermission
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
}
}
@@ -528,15 +527,14 @@ public class BroadcastSkipPolicy {
// Check that the receiver does *not* belong to any of the excluded packages
if (r.excludedPackages != null && r.excludedPackages.length > 0) {
if (ArrayUtils.contains(r.excludedPackages, filter.packageName)) {
- Slog.w(TAG, "Skipping delivery of excluded package "
+ return "Skipping delivery of excluded package "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
+ " excludes package " + filter.packageName
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
}
@@ -546,15 +544,14 @@ public class BroadcastSkipPolicy {
filter.receiverList.uid, filter.packageName, filter.featureId,
"Broadcast delivered to registered receiver " + filter.receiverId)
!= AppOpsManager.MODE_ALLOWED) {
- Slog.w(TAG, "Appop Denial: receiving "
+ return "Appop Denial: receiving "
+ r.intent.toString()
+ " to " + filter.receiverList.app
+ " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")"
+ " requires appop " + AppOpsManager.opToName(r.appOp)
+ " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")");
- return true;
+ + " (uid " + r.callingUid + ")";
}
// Ensure that broadcasts are only sent to other apps if they are explicitly marked as
@@ -562,15 +559,14 @@ public class BroadcastSkipPolicy {
if (!filter.exported && checkComponentPermission(null, r.callingPid,
r.callingUid, filter.receiverList.uid, filter.exported)
!= PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Exported Denial: sending "
+ return "Exported Denial: sending "
+ r.intent.toString()
+ ", action: " + r.intent.getAction()
+ " from " + r.callerPackage
+ " (uid=" + r.callingUid + ")"
+ " due to receiver " + filter.receiverList.app
+ " (uid " + filter.receiverList.uid + ")"
- + " not specifying RECEIVER_EXPORTED");
- return true;
+ + " not specifying RECEIVER_EXPORTED";
}
// If permissions need a review before any of the app components can run, we drop
@@ -579,10 +575,10 @@ public class BroadcastSkipPolicy {
// broadcast.
if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName,
filter.owningUserId)) {
- return true;
+ return "Skipping delivery to " + filter.packageName + " due to permissions review";
}
- return false;
+ return null;
}
private static String broadcastDescription(BroadcastRecord r, ComponentName component) {
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 740efbc658ba..14a169737b38 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -490,6 +490,11 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
final IApplicationThread finishedReceiverThread = caller;
boolean sendFinish = finishedReceiver != null;
+ if ((finishedReceiver != null) && (finishedReceiverThread == null)) {
+ Slog.w(TAG, "Sending of " + intent + " from " + Binder.getCallingUid()
+ + " requested resultTo without an IApplicationThread!", new Throwable());
+ }
+
int userId = key.userId;
if (userId == UserHandle.USER_CURRENT) {
userId = controller.mUserController.getCurrentOrTargetUserId();
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 71d39964ed72..f461f3d24b5f 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -62,6 +62,8 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.UUID;
+import java.util.concurrent.atomic.AtomicLong;
+
/**
* The error state of the process, such as if it's crashing/ANR etc.
*/
@@ -458,10 +460,10 @@ class ProcessErrorStateRecord {
// avoid spending 1/2 second collecting stats to rank lastPids.
StringWriter tracesFileException = new StringWriter();
// To hold the start and end offset to the ANR trace file respectively.
- final long[] offsets = new long[2];
+ final AtomicLong firstPidEndOffset = new AtomicLong(-1);
File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
- nativePids, tracesFileException, offsets, annotation, criticalEventLog,
+ nativePids, tracesFileException, firstPidEndOffset, annotation, criticalEventLog,
latencyTracker);
if (isMonitorCpuUsage()) {
@@ -478,10 +480,14 @@ class ProcessErrorStateRecord {
if (tracesFile == null) {
// There is no trace file, so dump (only) the alleged culprit's threads to the log
Process.sendSignal(pid, Process.SIGNAL_QUIT);
- } else if (offsets[1] > 0) {
+ } else if (firstPidEndOffset.get() > 0) {
// We've dumped into the trace file successfully
+ // We pass the start and end offsets of the first section of
+ // the ANR file (the headers and first process dump)
+ final long startOffset = 0L;
+ final long endOffset = firstPidEndOffset.get();
mService.mProcessList.mAppExitInfoTracker.scheduleLogAnrTrace(
- pid, mApp.uid, mApp.getPackageList(), tracesFile, offsets[0], offsets[1]);
+ pid, mApp.uid, mApp.getPackageList(), tracesFile, startOffset, endOffset);
}
// Check if package is still being loaded
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index a3b1a420b180..523a2dca7a18 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -699,7 +699,6 @@ public class DisplayDeviceConfig {
*/
public float getNitsFromBacklight(float backlight) {
if (mBacklightToNitsSpline == null) {
- Slog.wtf(TAG, "requesting nits when no mapping exists.");
return NITS_INVALID;
}
backlight = Math.max(backlight, mBacklightMinimum);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 9cb8f43c452c..0eaa5e452a58 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -144,6 +144,7 @@ import android.view.inputmethod.InputMethodSubtype;
import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.inputmethod.DirectBootAwareness;
@@ -1592,8 +1593,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
private final InputMethodManagerService mService;
public Lifecycle(Context context) {
+ this(context, new InputMethodManagerService(context));
+ }
+
+ public Lifecycle(
+ Context context, @NonNull InputMethodManagerService inputMethodManagerService) {
super(context);
- mService = new InputMethodManagerService(context);
+ mService = inputMethodManagerService;
}
@Override
@@ -1668,12 +1674,25 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
public InputMethodManagerService(Context context) {
+ this(context, null, null);
+ }
+
+ @VisibleForTesting
+ InputMethodManagerService(
+ Context context,
+ @Nullable ServiceThread serviceThreadForTesting,
+ @Nullable InputMethodBindingController bindingControllerForTesting) {
mContext = context;
mRes = context.getResources();
// TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
// additional subtypes in switchUserOnHandlerLocked().
- final ServiceThread thread = new ServiceThread(
- HANDLER_THREAD_NAME, Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+ final ServiceThread thread =
+ serviceThreadForTesting != null
+ ? serviceThreadForTesting
+ : new ServiceThread(
+ HANDLER_THREAD_NAME,
+ Process.THREAD_PRIORITY_FOREGROUND,
+ true /* allowIo */);
thread.start();
mHandler = Handler.createAsync(thread.getLooper(), this);
// Note: SettingsObserver doesn't register observers in its constructor.
@@ -1701,10 +1720,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
updateCurrentProfileIds();
AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
- mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
- mSettings, context);
+ mSwitchingController =
+ InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context);
mMenuController = new InputMethodMenuController(this);
- mBindingController = new InputMethodBindingController(this);
+ mBindingController =
+ bindingControllerForTesting != null
+ ? bindingControllerForTesting
+ : new InputMethodBindingController(this);
mAutofillController = new AutofillSuggestionsController(this);
mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
@@ -3673,6 +3695,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// UI for input.
if (isTextEditor && editorInfo != null
&& shouldRestoreImeVisibility(windowToken, softInputMode)) {
+ if (DEBUG) Slog.v(TAG, "Will show input to restore visibility");
res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection,
editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion,
imeDispatcher);
@@ -3719,11 +3742,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
imeDispatcher);
didStart = true;
}
- showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+ showCurrentInputLocked(
+ windowToken,
+ InputMethodManager.SHOW_IMPLICIT,
+ null,
SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV);
}
break;
case LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
+ if (DEBUG) {
+ Slog.v(TAG, "Window asks to keep the input in whatever state it was last in");
+ }
// Do nothing.
break;
case LayoutParams.SOFT_INPUT_STATE_HIDDEN:
@@ -3794,6 +3823,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// To maintain compatibility, we are now hiding the IME when we don't have
// an editor upon refocusing a window.
if (startInputByWinGainedFocus) {
+ if (DEBUG) Slog.v(TAG, "Same window without editor will hide input");
hideCurrentInputLocked(mCurFocusedWindow, 0, null,
SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
}
@@ -3807,6 +3837,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// 1) SOFT_INPUT_STATE_UNCHANGED state without an editor
// 2) SOFT_INPUT_STATE_VISIBLE state without an editor
// 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor
+ if (DEBUG) Slog.v(TAG, "Window without editor will hide input");
hideCurrentInputLocked(mCurFocusedWindow, 0, null,
SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR);
}
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 4c21195e2890..2e67bf2fcdf7 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -22,6 +22,15 @@ import static android.os.UserHandle.USER_NULL;
import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__BOOT;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__USER_CREATED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__USER_DELETED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__COMPAT_CHANGED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_ADDED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_DELETED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_REPLACED;
import static com.android.server.pm.AppsFilterUtils.canQueryAsInstaller;
import static com.android.server.pm.AppsFilterUtils.canQueryViaComponents;
import static com.android.server.pm.AppsFilterUtils.canQueryViaPackage;
@@ -36,6 +45,7 @@ import android.content.pm.PackageManagerInternal;
import android.content.pm.SigningDetails;
import android.content.pm.UserInfo;
import android.os.Handler;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
@@ -49,6 +59,7 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.FgThread;
import com.android.server.compat.CompatChange;
import com.android.server.om.OverlayReferenceMapper;
@@ -351,8 +362,15 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
if (pkg == null) {
return;
}
+ final long currentTimeUs = SystemClock.currentTimeMicro();
updateEnabledState(pkg);
mAppsFilter.updateShouldFilterCacheForPackage(snapshot, packageName);
+ mAppsFilter.logCacheUpdated(
+ PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__COMPAT_CHANGED,
+ SystemClock.currentTimeMicro() - currentTimeUs,
+ snapshot.getUserInfos().length,
+ snapshot.getPackageStates().size(),
+ pkg.getUid());
}
private void updateEnabledState(@NonNull AndroidPackage pkg) {
@@ -465,7 +483,8 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
mOverlayReferenceMapper.rebuildIfDeferred();
mFeatureConfig.onSystemReady();
- updateEntireShouldFilterCacheAsync(pmInternal);
+ updateEntireShouldFilterCacheAsync(pmInternal,
+ PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__BOOT);
}
/**
@@ -473,16 +492,23 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
*
* @param newPkgSetting the new setting being added
* @param isReplace if the package is being replaced and may need extra cleanup.
+ * @param retainImplicitGrantOnReplace {@code true} to retain implicit grant access if
+ * the package is being replaced.
*/
public void addPackage(Computer snapshot, PackageStateInternal newPkgSetting,
- boolean isReplace) {
+ boolean isReplace, boolean retainImplicitGrantOnReplace) {
+ final long currentTimeUs = SystemClock.currentTimeMicro();
+ final int logType = isReplace
+ ? PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_REPLACED
+ : PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_ADDED;
if (DEBUG_TRACING) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "filter.addPackage");
}
try {
if (isReplace) {
// let's first remove any prior rules for this package
- removePackage(snapshot, newPkgSetting, true /*isReplace*/);
+ removePackageInternal(snapshot, newPkgSetting,
+ true /*isReplace*/, retainImplicitGrantOnReplace);
}
final ArrayMap<String, ? extends PackageStateInternal> settings =
snapshot.getPackageStates();
@@ -508,6 +534,8 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
}
}
}
+ logCacheUpdated(logType, SystemClock.currentTimeMicro() - currentTimeUs,
+ users.length, settings.size(), newPkgSetting.getAppId());
} else {
invalidateCache("addPackage: " + newPkgSetting.getPackageName());
}
@@ -757,18 +785,19 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
}
}
- private void updateEntireShouldFilterCacheAsync(PackageManagerInternal pmInternal) {
- updateEntireShouldFilterCacheAsync(pmInternal, CACHE_REBUILD_DELAY_MIN_MS);
+ private void updateEntireShouldFilterCacheAsync(PackageManagerInternal pmInternal, int reason) {
+ updateEntireShouldFilterCacheAsync(pmInternal, CACHE_REBUILD_DELAY_MIN_MS, reason);
}
private void updateEntireShouldFilterCacheAsync(PackageManagerInternal pmInternal,
- long delayMs) {
+ long delayMs, int reason) {
mBackgroundHandler.postDelayed(() -> {
if (!mCacheValid.compareAndSet(CACHE_INVALID, CACHE_VALID)) {
// Cache is already valid.
return;
}
+ final long currentTimeUs = SystemClock.currentTimeMicro();
final ArrayMap<String, AndroidPackage> packagesCache = new ArrayMap<>();
final UserInfo[][] usersRef = new UserInfo[1][];
final Computer snapshot = (Computer) pmInternal.snapshot();
@@ -787,11 +816,13 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
updateEntireShouldFilterCacheInner(snapshot, settings, usersRef[0], USER_ALL);
onChanged();
+ logCacheRebuilt(reason, SystemClock.currentTimeMicro() - currentTimeUs,
+ users.length, settings.size());
if (!mCacheValid.compareAndSet(CACHE_VALID, CACHE_VALID)) {
Slog.i(TAG, "Cache invalidated while building, retrying.");
updateEntireShouldFilterCacheAsync(pmInternal,
- Math.min(delayMs * 2, CACHE_REBUILD_DELAY_MAX_MS));
+ Math.min(delayMs * 2, CACHE_REBUILD_DELAY_MAX_MS), reason);
return;
}
@@ -803,15 +834,27 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
if (!mCacheReady) {
return;
}
+ final long currentTimeUs = SystemClock.currentTimeMicro();
updateEntireShouldFilterCache(snapshot, newUserId);
+ logCacheRebuilt(
+ PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__USER_CREATED,
+ SystemClock.currentTimeMicro() - currentTimeUs,
+ snapshot.getUserInfos().length,
+ snapshot.getPackageStates().size());
}
- public void onUserDeleted(@UserIdInt int userId) {
+ public void onUserDeleted(Computer snapshot, @UserIdInt int userId) {
if (!mCacheReady) {
return;
}
+ final long currentTimeUs = SystemClock.currentTimeMicro();
removeShouldFilterCacheForUser(userId);
onChanged();
+ logCacheRebuilt(
+ PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__USER_DELETED,
+ SystemClock.currentTimeMicro() - currentTimeUs,
+ snapshot.getUserInfos().length,
+ snapshot.getPackageStates().size());
}
private void updateShouldFilterCacheForPackage(Computer snapshot,
@@ -976,13 +1019,31 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
}
/**
- * Equivalent to calling {@link #addPackage(Computer, PackageStateInternal, boolean)}
- * with {@code isReplace} equal to {@code false}.
+ * Equivalent to calling {@link #addPackage(Computer, PackageStateInternal, boolean, boolean)}
+ * with {@code isReplace} and {@code retainImplicitGrantOnReplace} equal to {@code false}.
*
- * @see AppsFilterImpl#addPackage(Computer, PackageStateInternal, boolean)
+ * @see AppsFilterImpl#addPackage(Computer, PackageStateInternal, boolean, boolean)
*/
public void addPackage(Computer snapshot, PackageStateInternal newPkgSetting) {
- addPackage(snapshot, newPkgSetting, false /* isReplace */);
+ addPackage(snapshot, newPkgSetting, false /* isReplace */,
+ false /* retainImplicitGrantOnReplace */);
+ }
+
+ /**
+ * Removes a package for consideration when filtering visibility between apps.
+ *
+ * @param setting the setting of the package being removed.
+ */
+ public void removePackage(Computer snapshot, PackageStateInternal setting) {
+ final long currentTimeUs = SystemClock.currentTimeMicro();
+ removePackageInternal(snapshot, setting,
+ false /* isReplace */, false /* retainImplicitGrantOnReplace */);
+ logCacheUpdated(
+ PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_DELETED,
+ SystemClock.currentTimeMicro() - currentTimeUs,
+ snapshot.getUserInfos().length,
+ snapshot.getPackageStates().size(),
+ setting.getAppId());
}
/**
@@ -990,33 +1051,37 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
*
* @param setting the setting of the package being removed.
* @param isReplace if the package is being replaced.
+ * @param retainImplicitGrantOnReplace {@code true} to retain implicit grant access if
+ * the package is being replaced.
*/
- public void removePackage(Computer snapshot, PackageStateInternal setting,
- boolean isReplace) {
+ private void removePackageInternal(Computer snapshot, PackageStateInternal setting,
+ boolean isReplace, boolean retainImplicitGrantOnReplace) {
final ArraySet<String> additionalChangedPackages;
final ArrayMap<String, ? extends PackageStateInternal> settings =
snapshot.getPackageStates();
final UserInfo[] users = snapshot.getUserInfos();
final Collection<SharedUserSetting> sharedUserSettings = snapshot.getAllSharedUsers();
final int userCount = users.length;
- synchronized (mImplicitlyQueryableLock) {
- for (int u = 0; u < userCount; u++) {
- final int userId = users[u].id;
- final int removingUid = UserHandle.getUid(userId, setting.getAppId());
- mImplicitlyQueryable.remove(removingUid);
- for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) {
- mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i),
- removingUid);
- }
+ if (!isReplace || !retainImplicitGrantOnReplace) {
+ synchronized (mImplicitlyQueryableLock) {
+ for (int u = 0; u < userCount; u++) {
+ final int userId = users[u].id;
+ final int removingUid = UserHandle.getUid(userId, setting.getAppId());
+ mImplicitlyQueryable.remove(removingUid);
+ for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) {
+ mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i),
+ removingUid);
+ }
- if (isReplace) {
- continue;
- }
+ if (isReplace) {
+ continue;
+ }
- mRetainedImplicitlyQueryable.remove(removingUid);
- for (int i = mRetainedImplicitlyQueryable.size() - 1; i >= 0; i--) {
- mRetainedImplicitlyQueryable.remove(
- mRetainedImplicitlyQueryable.keyAt(i), removingUid);
+ mRetainedImplicitlyQueryable.remove(removingUid);
+ for (int i = mRetainedImplicitlyQueryable.size() - 1; i >= 0; i--) {
+ mRetainedImplicitlyQueryable.remove(
+ mRetainedImplicitlyQueryable.keyAt(i), removingUid);
+ }
}
}
}
@@ -1174,4 +1239,18 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
}
}
}
+
+ private void logCacheRebuilt(int eventId, long latency, int userCount, int packageCount) {
+ FrameworkStatsLog.write(PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED,
+ eventId, latency, userCount, packageCount, mShouldFilterCache.size());
+ }
+
+ private void logCacheUpdated(int eventId, long latency, int userCount, int packageCount,
+ int appId) {
+ if (!mCacheReady) {
+ return;
+ }
+ FrameworkStatsLog.write(PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED,
+ eventId, appId, latency, userCount, packageCount, mShouldFilterCache.size());
+ }
}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index e7412c5e52ec..d856d54cb4e0 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -410,7 +410,7 @@ public final class BackgroundDexOptService {
job.jobFinished(params, !completed);
} else {
// Periodic job
- job.jobFinished(params, true);
+ job.jobFinished(params, false /* reschedule */);
}
markDexOptCompleted();
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 9d007c92bcc5..7ea0c04562e2 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -463,7 +463,8 @@ final class InstallPackageHelper {
final Computer snapshot = mPm.snapshotComputer();
mPm.mComponentResolver.addAllComponents(pkg, chatty, mPm.mSetupWizardPackage, snapshot);
- mPm.mAppsFilter.addPackage(snapshot, pkgSetting, isReplace);
+ mPm.mAppsFilter.addPackage(snapshot, pkgSetting, isReplace,
+ (scanFlags & SCAN_DONT_KILL_APP) != 0 /* retainImplicitGrantOnReplace */);
mPm.addAllPackageProperties(pkg);
if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5df73a6d1fbd..0369a83ec9c1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3326,7 +3326,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
"Failure to obtain package info.");
}
final List<String> filePaths = packageLite.getAllApkPaths();
- final String appLabel = mPreapprovalDetails.getLabel();
+ final CharSequence appLabel = mPreapprovalDetails.getLabel();
final ULocale appLocale = mPreapprovalDetails.getLocale();
final ApplicationInfo appInfo = packageInfo.applicationInfo;
boolean appLabelMatched = false;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index dfbe68a8e997..23cf26236e87 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1147,7 +1147,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
var done = SystemClock.currentTimeMicro();
if (mSnapshotStatistics != null) {
- mSnapshotStatistics.rebuild(now, done, hits);
+ mSnapshotStatistics.rebuild(now, done, hits, newSnapshot.getPackageStates().size());
}
return newSnapshot;
}
@@ -4220,7 +4220,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mSettings.removeUserLPw(userId);
mPendingBroadcasts.remove(userId);
mDeletePackageHelper.removeUnusedPackagesLPw(userManager, userId);
- mAppsFilter.onUserDeleted(userId);
+ mAppsFilter.onUserDeleted(snapshotComputer(), userId);
}
mInstantAppRegistry.onUserRemoved(userId);
}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index bbc4fdeb36bb..7e936735ef7d 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -308,7 +308,7 @@ final class RemovePackageHelper {
mPm.mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
final Computer snapshot = mPm.snapshotComputer();
mPm.mAppsFilter.removePackage(snapshot,
- snapshot.getPackageStateInternal(packageName), false /* isReplace */);
+ snapshot.getPackageStateInternal(packageName));
removedAppId = mPm.mSettings.removePackageLPw(packageName);
if (outInfo != null) {
outInfo.mRemovedAppId = removedAppId;
diff --git a/services/core/java/com/android/server/pm/SnapshotStatistics.java b/services/core/java/com/android/server/pm/SnapshotStatistics.java
index 2cfc8946ad02..e04a1e5b3569 100644
--- a/services/core/java/com/android/server/pm/SnapshotStatistics.java
+++ b/services/core/java/com/android/server/pm/SnapshotStatistics.java
@@ -24,11 +24,13 @@ import android.os.SystemClock;
import android.text.TextUtils;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.EventLogTags;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Locale;
+import java.util.concurrent.TimeUnit;
/**
* This class records statistics about PackageManagerService snapshots. It maintains two sets of
@@ -59,9 +61,9 @@ public class SnapshotStatistics {
public static final int SNAPSHOT_TICK_INTERVAL_MS = 60 * 1000;
/**
- * The number of ticks for long statistics. This is one week.
+ * The interval of the snapshot statistics logging.
*/
- public static final int SNAPSHOT_LONG_TICKS = 7 * 24 * 60;
+ private static final long SNAPSHOT_LOG_INTERVAL_US = TimeUnit.DAYS.toMicros(1);
/**
* The number snapshot event logs that can be generated in a single logging interval.
@@ -93,6 +95,28 @@ public class SnapshotStatistics {
public static final int SNAPSHOT_SHORT_LIFETIME = 5;
/**
+ * Buckets to represent a range of the rebuild latency for the histogram of
+ * snapshot rebuild latency.
+ */
+ private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_1_MILLIS = 1;
+ private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_2_MILLIS = 2;
+ private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_5_MILLIS = 5;
+ private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_10_MILLIS = 10;
+ private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_20_MILLIS = 20;
+ private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_50_MILLIS = 50;
+ private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_100_MILLIS = 100;
+
+ /**
+ * Buckets to represent a range of the reuse count for the histogram of
+ * snapshot reuse counts.
+ */
+ private static final int REUSE_COUNT_BUCKET_LESS_THAN_1 = 1;
+ private static final int REUSE_COUNT_BUCKET_LESS_THAN_10 = 10;
+ private static final int REUSE_COUNT_BUCKET_LESS_THAN_100 = 100;
+ private static final int REUSE_COUNT_BUCKET_LESS_THAN_1000 = 1000;
+ private static final int REUSE_COUNT_BUCKET_LESS_THAN_10000 = 10000;
+
+ /**
* The lock to control access to this object.
*/
private final Object mLock = new Object();
@@ -113,11 +137,6 @@ public class SnapshotStatistics {
private int mEventsReported = 0;
/**
- * The tick counter. At the default tick interval, this wraps every 4000 years or so.
- */
- private int mTicks = 0;
-
- /**
* The handler used for the periodic ticks.
*/
private Handler mHandler = null;
@@ -139,8 +158,6 @@ public class SnapshotStatistics {
// The number of bins
private int mCount;
- // The mapping of low integers to bins
- private int[] mBinMap;
// The maximum mapped value. Values at or above this are mapped to the
// top bin.
private int mMaxBin;
@@ -158,16 +175,6 @@ public class SnapshotStatistics {
mCount = mUserKey.length + 1;
// The maximum value is one more than the last one in the map.
mMaxBin = mUserKey[mUserKey.length - 1] + 1;
- mBinMap = new int[mMaxBin + 1];
-
- int j = 0;
- for (int i = 0; i < mUserKey.length; i++) {
- while (j <= mUserKey[i]) {
- mBinMap[j] = i;
- j++;
- }
- }
- mBinMap[mMaxBin] = mUserKey.length;
}
/**
@@ -175,9 +182,14 @@ public class SnapshotStatistics {
*/
public int getBin(int x) {
if (x >= 0 && x < mMaxBin) {
- return mBinMap[x];
+ for (int i = 0; i < mUserKey.length; i++) {
+ if (x <= mUserKey[i]) {
+ return i;
+ }
+ }
+ return 0; // should not happen
} else if (x >= mMaxBin) {
- return mBinMap[mMaxBin];
+ return mUserKey.length;
} else {
// x is negative. The bin will not be used.
return 0;
@@ -263,6 +275,11 @@ public class SnapshotStatistics {
public int mMaxBuildTimeUs = 0;
/**
+ * The maximum used count since the last log.
+ */
+ public int mMaxUsedCount = 0;
+
+ /**
* Record the rebuild. The parameters are the length of time it took to build the
* latest snapshot, and the number of times the _previous_ snapshot was used. A
* negative value for used signals an invalid value, which is the case the first
@@ -279,7 +296,6 @@ public class SnapshotStatistics {
}
mTotalTimeUs += duration;
- boolean reportIt = false;
if (big) {
mBigBuilds++;
@@ -290,6 +306,9 @@ public class SnapshotStatistics {
if (mMaxBuildTimeUs < duration) {
mMaxBuildTimeUs = duration;
}
+ if (mMaxUsedCount < used) {
+ mMaxUsedCount = used;
+ }
}
private Stats(long now) {
@@ -313,6 +332,7 @@ public class SnapshotStatistics {
mShortLived = orig.mShortLived;
mTotalTimeUs = orig.mTotalTimeUs;
mMaxBuildTimeUs = orig.mMaxBuildTimeUs;
+ mMaxUsedCount = orig.mMaxUsedCount;
}
/**
@@ -443,18 +463,19 @@ public class SnapshotStatistics {
}
/**
- * Report the object via an event. Presumably the record indicates an anomalous
- * incident.
+ * Report the snapshot statistics to FrameworkStatsLog.
*/
- private void report() {
- EventLogTags.writePmSnapshotStats(
- mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived,
- mMaxBuildTimeUs / US_IN_MS, mTotalTimeUs / US_IN_MS);
+ private void logSnapshotStatistics(int packageCount) {
+ final long avgLatencyUs = (mTotalBuilds == 0 ? 0 : mTotalTimeUs / mTotalBuilds);
+ final int avgUsedCount = (mTotalBuilds == 0 ? 0 : mTotalUsed / mTotalBuilds);
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.PACKAGE_MANAGER_SNAPSHOT_REPORTED, mTimes, mUsed,
+ mMaxBuildTimeUs, mMaxUsedCount, avgLatencyUs, avgUsedCount, packageCount);
}
}
/**
- * Long statistics. These roll over approximately every week.
+ * Long statistics. These roll over approximately one day.
*/
private Stats[] mLong;
@@ -464,10 +485,14 @@ public class SnapshotStatistics {
private Stats[] mShort;
/**
- * The time of the last build. This can be used to compute the length of time a
- * snapshot existed before being replaced.
+ * The time of last logging to the FrameworkStatsLog.
*/
- private long mLastBuildTime = 0;
+ private long mLastLogTimeUs;
+
+ /**
+ * The number of packages on the device.
+ */
+ private int mPackageCount;
/**
* Create a snapshot object. Initialize the bin levels. The last bin catches
@@ -475,8 +500,20 @@ public class SnapshotStatistics {
*/
public SnapshotStatistics() {
// Create the bin thresholds. The time bins are in units of us.
- mTimeBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 });
- mUseBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 });
+ mTimeBins = new BinMap(new int[] {
+ REBUILD_LATENCY_BUCKET_LESS_THAN_1_MILLIS,
+ REBUILD_LATENCY_BUCKET_LESS_THAN_2_MILLIS,
+ REBUILD_LATENCY_BUCKET_LESS_THAN_5_MILLIS,
+ REBUILD_LATENCY_BUCKET_LESS_THAN_10_MILLIS,
+ REBUILD_LATENCY_BUCKET_LESS_THAN_20_MILLIS,
+ REBUILD_LATENCY_BUCKET_LESS_THAN_50_MILLIS,
+ REBUILD_LATENCY_BUCKET_LESS_THAN_100_MILLIS });
+ mUseBins = new BinMap(new int[] {
+ REUSE_COUNT_BUCKET_LESS_THAN_1,
+ REUSE_COUNT_BUCKET_LESS_THAN_10,
+ REUSE_COUNT_BUCKET_LESS_THAN_100,
+ REUSE_COUNT_BUCKET_LESS_THAN_1000,
+ REUSE_COUNT_BUCKET_LESS_THAN_10000 });
// Create the raw statistics
final long now = SystemClock.currentTimeMicro();
@@ -484,6 +521,7 @@ public class SnapshotStatistics {
mLong[0] = new Stats(now);
mShort = new Stats[10];
mShort[0] = new Stats(now);
+ mLastLogTimeUs = now;
// Create the message handler for ticks and start the ticker.
mHandler = new Handler(Looper.getMainLooper()) {
@@ -516,13 +554,14 @@ public class SnapshotStatistics {
* @param now The time at which the snapshot rebuild began, in ns.
* @param done The time at which the snapshot rebuild completed, in ns.
* @param hits The number of times the previous snapshot was used.
+ * @param packageCount The number of packages on the device.
*/
- public final void rebuild(long now, long done, int hits) {
+ public final void rebuild(long now, long done, int hits, int packageCount) {
// The duration has a span of about 2000s
final int duration = (int) (done - now);
boolean reportEvent = false;
synchronized (mLock) {
- mLastBuildTime = now;
+ mPackageCount = packageCount;
final int timeBin = mTimeBins.getBin(duration / 1000);
final int useBin = mUseBins.getBin(hits);
@@ -570,10 +609,12 @@ public class SnapshotStatistics {
private void tick() {
synchronized (mLock) {
long now = SystemClock.currentTimeMicro();
- mTicks++;
- if (mTicks % SNAPSHOT_LONG_TICKS == 0) {
+ if (now - mLastLogTimeUs > SNAPSHOT_LOG_INTERVAL_US) {
shift(mLong, now);
+ mLastLogTimeUs = now;
+ mLong[mLong.length - 1].logSnapshotStatistics(mPackageCount);
}
+
shift(mShort, now);
mEventsReported = 0;
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index abb57bca3704..6a3553315e5a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -320,9 +320,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
@Override
public void sendResult(Bundle data) throws RemoteException {
- if (DEBUG) {
- Slog.d(TAG, "publish system wallpaper changed!");
- }
+ Slog.d(TAG, "publish system wallpaper changed!");
notifyWallpaperChanged(wallpaper);
}
};
@@ -1552,6 +1550,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
mReply.sendResult(null);
} catch (RemoteException e) {
Binder.restoreCallingIdentity(ident);
+ Slog.d(TAG, "failed to send callback!", e);
}
mReply = null;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ecc43f7b938b..b153a85a4048 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1316,7 +1316,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mAppSwitchesState = APP_SWITCH_ALLOW;
}
}
- return pir.sendInner(0, fillInIntent, resolvedType, allowlistToken, null, null,
+ return pir.sendInner(caller, 0, fillInIntent, resolvedType, allowlistToken, null, null,
resultTo, resultWho, requestCode, flagsMask, flagsValues, bOptions);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ccfb9299b565..f30c4355306c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5709,6 +5709,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return super.getAnimationLeashParent();
}
+ @Override
+ public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
+ super.onAnimationLeashCreated(t, leash);
+ if (isStartingWindowAssociatedToTask()) {
+ // Make sure the animation leash is still on top of the task.
+ t.setLayer(leash, Integer.MAX_VALUE);
+ }
+ }
+
// TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA
// then we can drop all negative layering on the windowing side and simply inherit
// the default implementation here.
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
new file mode 100644
index 000000000000..939fb6a9d170
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -0,0 +1,62 @@
+// 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 {
+ // 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"],
+}
+
+android_test {
+ name: "FrameworksInputMethodSystemServerTests",
+ defaults: [
+ "modules-utils-testable-device-config-defaults",
+ ],
+
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.runner",
+ "androidx.test.espresso.core",
+ "androidx.test.espresso.contrib",
+ "androidx.test.ext.truth",
+ "frameworks-base-testutils",
+ "mockito-target-extended-minus-junit4",
+ "platform-test-annotations",
+ "services.core",
+ "servicestests-core-utils",
+ "servicestests-utils-mockito-extended",
+ "truth-prebuilt",
+ ],
+
+ libs: [
+ "android.test.mock",
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: ["device-tests"],
+
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
new file mode 100644
index 000000000000..12e7cfc28a56
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.inputmethodtests">
+
+ <uses-sdk android:targetSdkVersion="31" />
+
+ <!-- Permissions required for granting and logging -->
+ <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
+ <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
+ <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
+ <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+
+ <!-- Permissions for reading system info -->
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+
+ <application android:testOnly="true"
+ android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.frameworks.inputmethodtests"
+ android:label="Frameworks InputMethod System Service Tests" />
+
+</manifest>
diff --git a/services/tests/InputMethodSystemServerTests/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/AndroidTest.xml
new file mode 100644
index 000000000000..92be78060da8
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/AndroidTest.xml
@@ -0,0 +1,39 @@
+<?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.
+-->
+<configuration description="Runs Frameworks InputMethod System Services Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="FrameworksInputMethodSystemServerTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="FrameworksInputMethodSystemServerTests" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.frameworks.inputmethodtests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+
+ <!-- Collect the files in the dump directory for debugging -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/sdcard/FrameworksInputMethodSystemServerTests/" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+</configuration>
diff --git a/services/tests/InputMethodSystemServerTests/OWNERS b/services/tests/InputMethodSystemServerTests/OWNERS
new file mode 100644
index 000000000000..1f2c036773a4
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
new file mode 100644
index 000000000000..3fbc4004785d
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -0,0 +1,259 @@
+/*
+ * 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.inputmethod;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.view.inputmethod.EditorInfo;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.inputmethod.IInputMethod;
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.view.IInputMethodManager;
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.SystemService;
+import com.android.server.input.InputManagerInternal;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+/** Base class for testing {@link InputMethodManagerService}. */
+public class InputMethodManagerServiceTestBase {
+ protected static final String TEST_SELECTED_IME_ID = "test.ime";
+ protected static final String TEST_EDITOR_PKG_NAME = "test.editor";
+ protected static final String TEST_FOCUSED_WINDOW_NAME = "test.editor/activity";
+ protected static final WindowManagerInternal.ImeTargetInfo TEST_IME_TARGET_INFO =
+ new WindowManagerInternal.ImeTargetInfo(
+ TEST_FOCUSED_WINDOW_NAME,
+ TEST_FOCUSED_WINDOW_NAME,
+ TEST_FOCUSED_WINDOW_NAME,
+ TEST_FOCUSED_WINDOW_NAME,
+ TEST_FOCUSED_WINDOW_NAME);
+ protected static final InputBindResult SUCCESS_WAITING_IME_BINDING_RESULT =
+ new InputBindResult(
+ InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
+ null,
+ null,
+ null,
+ "0",
+ 0,
+ null,
+ false);
+
+ @Mock protected WindowManagerInternal mMockWindowManagerInternal;
+ @Mock protected ActivityManagerInternal mMockActivityManagerInternal;
+ @Mock protected PackageManagerInternal mMockPackageManagerInternal;
+ @Mock protected InputManagerInternal mMockInputManagerInternal;
+ @Mock protected DisplayManagerInternal mMockDisplayManagerInternal;
+ @Mock protected UserManagerInternal mMockUserManagerInternal;
+ @Mock protected InputMethodBindingController mMockInputMethodBindingController;
+ @Mock protected IInputMethodClient mMockInputMethodClient;
+ @Mock protected IBinder mWindowToken;
+ @Mock protected IRemoteInputConnection mMockRemoteInputConnection;
+ @Mock protected IRemoteAccessibilityInputConnection mMockRemoteAccessibilityInputConnection;
+ @Mock protected ImeOnBackInvokedDispatcher mMockImeOnBackInvokedDispatcher;
+ @Mock protected IInputMethodManager.Stub mMockIInputMethodManager;
+ @Mock protected IPlatformCompat.Stub mMockIPlatformCompat;
+ @Mock protected IInputMethod mMockInputMethod;
+ @Mock protected IBinder mMockInputMethodBinder;
+ @Mock protected IInputManager mMockIInputManager;
+
+ protected Context mContext;
+ protected MockitoSession mMockingSession;
+ protected int mTargetSdkVersion;
+ protected int mCallingUserId;
+ protected EditorInfo mEditorInfo;
+ protected IInputMethodInvoker mMockInputMethodInvoker;
+ protected InputMethodManagerService mInputMethodManagerService;
+ protected ServiceThread mServiceThread;
+
+ @Before
+ public void setUp() throws RemoteException {
+ mMockingSession =
+ mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .mockStatic(LocalServices.class)
+ .mockStatic(ServiceManager.class)
+ .mockStatic(SystemServerInitThreadPool.class)
+ .startMocking();
+
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ spyOn(mContext);
+
+ mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
+ mCallingUserId = UserHandle.getCallingUserId();
+ mEditorInfo = new EditorInfo();
+ mEditorInfo.packageName = TEST_EDITOR_PKG_NAME;
+
+ // Injecting and mocking local services.
+ doReturn(mMockWindowManagerInternal)
+ .when(() -> LocalServices.getService(WindowManagerInternal.class));
+ doReturn(mMockActivityManagerInternal)
+ .when(() -> LocalServices.getService(ActivityManagerInternal.class));
+ doReturn(mMockPackageManagerInternal)
+ .when(() -> LocalServices.getService(PackageManagerInternal.class));
+ doReturn(mMockInputManagerInternal)
+ .when(() -> LocalServices.getService(InputManagerInternal.class));
+ doReturn(mMockDisplayManagerInternal)
+ .when(() -> LocalServices.getService(DisplayManagerInternal.class));
+ doReturn(mMockUserManagerInternal)
+ .when(() -> LocalServices.getService(UserManagerInternal.class));
+ doReturn(mMockIInputMethodManager)
+ .when(() -> ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));
+ doReturn(mMockIPlatformCompat)
+ .when(() -> ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+
+ // Stubbing out context related methods to avoid the system holding strong references to
+ // InputMethodManagerService.
+ doNothing().when(mContext).enforceCallingPermission(anyString(), anyString());
+ doNothing().when(mContext).sendBroadcastAsUser(any(), any());
+ doReturn(null).when(mContext).registerReceiver(any(), any());
+ doReturn(null)
+ .when(mContext)
+ .registerReceiverAsUser(any(), any(), any(), anyString(), any(), anyInt());
+
+ // Injecting and mocked InputMethodBindingController and InputMethod.
+ mMockInputMethodInvoker = IInputMethodInvoker.create(mMockInputMethod);
+ InputManager.resetInstance(mMockIInputManager);
+ synchronized (ImfLock.class) {
+ when(mMockInputMethodBindingController.getCurMethod())
+ .thenReturn(mMockInputMethodInvoker);
+ when(mMockInputMethodBindingController.bindCurrentMethod())
+ .thenReturn(SUCCESS_WAITING_IME_BINDING_RESULT);
+ doNothing().when(mMockInputMethodBindingController).unbindCurrentMethod();
+ when(mMockInputMethodBindingController.getSelectedMethodId())
+ .thenReturn(TEST_SELECTED_IME_ID);
+ }
+
+ // Shuffling around all other initialization to make the test runnable.
+ when(mMockIInputManager.getInputDeviceIds()).thenReturn(new int[0]);
+ when(mMockIInputMethodManager.isImeTraceEnabled()).thenReturn(false);
+ when(mMockIPlatformCompat.isChangeEnabledByUid(anyLong(), anyInt())).thenReturn(true);
+ when(mMockUserManagerInternal.isUserRunning(anyInt())).thenReturn(true);
+ when(mMockUserManagerInternal.getProfileIds(anyInt(), anyBoolean()))
+ .thenReturn(new int[] {0});
+ when(mMockActivityManagerInternal.isSystemReady()).thenReturn(true);
+ when(mMockPackageManagerInternal.getPackageUid(anyString(), anyLong(), anyInt()))
+ .thenReturn(Binder.getCallingUid());
+ when(mMockWindowManagerInternal.onToggleImeRequested(anyBoolean(), any(), any(), anyInt()))
+ .thenReturn(TEST_IME_TARGET_INFO);
+ when(mMockInputMethodClient.asBinder()).thenReturn(mMockInputMethodBinder);
+
+ // Used by lazy initializing draw IMS nav bar at InputMethodManagerService#systemRunning(),
+ // which is ok to be mocked out for now.
+ doReturn(null).when(() -> SystemServerInitThreadPool.submit(any(), anyString()));
+
+ mServiceThread =
+ new ServiceThread(
+ "TestServiceThread",
+ Process.THREAD_PRIORITY_FOREGROUND, /* allowIo */
+ false);
+ mInputMethodManagerService =
+ new InputMethodManagerService(
+ mContext, mServiceThread, mMockInputMethodBindingController);
+
+ // Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of
+ // InputMethodManagerService, which is closer to the real situation.
+ InputMethodManagerService.Lifecycle lifecycle =
+ new InputMethodManagerService.Lifecycle(mContext, mInputMethodManagerService);
+
+ // Public local InputMethodManagerService.
+ lifecycle.onStart();
+ try {
+ // After this boot phase, services can broadcast Intents.
+ lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+ } catch (SecurityException e) {
+ // Security exception to permission denial is expected in test, mocking out to ensure
+ // InputMethodManagerService as system ready state.
+ if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+ throw e;
+ }
+ }
+
+ // Call InputMethodManagerService#addClient() as a preparation to start interacting with it.
+ mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection, 0);
+ }
+
+ @After
+ public void tearDown() {
+ if (mServiceThread != null) {
+ mServiceThread.quitSafely();
+ }
+
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput)
+ throws RemoteException {
+ synchronized (ImfLock.class) {
+ verify(mMockInputMethodBindingController, times(setVisible ? 1 : 0))
+ .setCurrentMethodVisible();
+ }
+ verify(mMockInputMethod, times(showSoftInput ? 1 : 0))
+ .showSoftInput(any(), anyInt(), any());
+ }
+
+ protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput)
+ throws RemoteException {
+ synchronized (ImfLock.class) {
+ verify(mMockInputMethodBindingController, times(setNotVisible ? 1 : 0))
+ .setCurrentMethodNotVisible();
+ }
+ verify(mMockInputMethod, times(hideSoftInput ? 1 : 0))
+ .hideSoftInput(any(), anyInt(), any());
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
new file mode 100644
index 000000000000..ffa272943455
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
@@ -0,0 +1,266 @@
+/*
+ * 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.inputmethod;
+
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test the behavior of {@link InputMethodManagerService#startInputOrWindowGainedFocus(int,
+ * IInputMethodClient, IBinder, int, int, int, EditorInfo, IRemoteInputConnection,
+ * IRemoteAccessibilityInputConnection, int, int, ImeOnBackInvokedDispatcher)}.
+ */
+@RunWith(Parameterized.class)
+public class InputMethodManagerServiceWindowGainedFocusTest
+ extends InputMethodManagerServiceTestBase {
+ private static final String TAG = "IMMSWindowGainedFocusTest";
+
+ private static final int[] SOFT_INPUT_STATE_FLAGS =
+ new int[] {
+ SOFT_INPUT_STATE_UNSPECIFIED,
+ SOFT_INPUT_STATE_UNCHANGED,
+ SOFT_INPUT_STATE_HIDDEN,
+ SOFT_INPUT_STATE_ALWAYS_HIDDEN,
+ SOFT_INPUT_STATE_VISIBLE,
+ SOFT_INPUT_STATE_ALWAYS_VISIBLE
+ };
+ private static final int[] SOFT_INPUT_ADJUST_FLAGS =
+ new int[] {
+ SOFT_INPUT_ADJUST_UNSPECIFIED,
+ SOFT_INPUT_ADJUST_RESIZE,
+ SOFT_INPUT_ADJUST_PAN,
+ SOFT_INPUT_ADJUST_NOTHING
+ };
+ private static final int DEFAULT_SOFT_INPUT_FLAG =
+ StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR;
+
+ @Parameterized.Parameters(name = "softInputState={0}, softInputAdjustment={1}")
+ public static List<Object[]> softInputModeConfigs() {
+ ArrayList<Object[]> params = new ArrayList<>();
+ for (int softInputState : SOFT_INPUT_STATE_FLAGS) {
+ for (int softInputAdjust : SOFT_INPUT_ADJUST_FLAGS) {
+ params.add(new Object[] {softInputState, softInputAdjust});
+ }
+ }
+ return params;
+ }
+
+ private final int mSoftInputState;
+ private final int mSoftInputAdjustment;
+
+ public InputMethodManagerServiceWindowGainedFocusTest(
+ int softInputState, int softInputAdjustment) {
+ mSoftInputState = softInputState;
+ mSoftInputAdjustment = softInputAdjustment;
+ }
+
+ @Test
+ public void startInputOrWindowGainedFocus_forwardNavigation() throws RemoteException {
+ mockHasImeFocusAndRestoreImeVisibility(false /* restoreImeVisibility */);
+
+ assertThat(
+ startInputOrWindowGainedFocus(
+ DEFAULT_SOFT_INPUT_FLAG, true /* forwardNavigation */))
+ .isEqualTo(SUCCESS_WAITING_IME_BINDING_RESULT);
+
+ switch (mSoftInputState) {
+ case SOFT_INPUT_STATE_UNSPECIFIED:
+ boolean showSoftInput = mSoftInputAdjustment == SOFT_INPUT_ADJUST_RESIZE;
+ verifyShowSoftInput(
+ showSoftInput /* setVisible */, showSoftInput /* showSoftInput */);
+ // Soft input was hidden by default, so it doesn't need to call
+ // {@code IMS#hideSoftInput()}.
+ verifyHideSoftInput(!showSoftInput /* setNotVisible */, false /* hideSoftInput */);
+ break;
+ case SOFT_INPUT_STATE_VISIBLE:
+ case SOFT_INPUT_STATE_ALWAYS_VISIBLE:
+ verifyShowSoftInput(true /* setVisible */, true /* showSoftInput */);
+ verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+ break;
+ case SOFT_INPUT_STATE_UNCHANGED: // Do nothing
+ verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+ verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+ break;
+ case SOFT_INPUT_STATE_HIDDEN:
+ case SOFT_INPUT_STATE_ALWAYS_HIDDEN:
+ verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+ // Soft input was hidden by default, so it doesn't need to call
+ // {@code IMS#hideSoftInput()}.
+ verifyHideSoftInput(true /* setNotVisible */, false /* hideSoftInput */);
+ break;
+ default:
+ throw new IllegalStateException(
+ "Unhandled soft input mode: "
+ + InputMethodDebug.softInputModeToString(mSoftInputState));
+ }
+ }
+
+ @Test
+ public void startInputOrWindowGainedFocus_notForwardNavigation() throws RemoteException {
+ mockHasImeFocusAndRestoreImeVisibility(false /* restoreImeVisibility */);
+
+ assertThat(
+ startInputOrWindowGainedFocus(
+ DEFAULT_SOFT_INPUT_FLAG, false /* forwardNavigation */))
+ .isEqualTo(SUCCESS_WAITING_IME_BINDING_RESULT);
+
+ switch (mSoftInputState) {
+ case SOFT_INPUT_STATE_UNSPECIFIED:
+ boolean hideSoftInput = mSoftInputAdjustment != SOFT_INPUT_ADJUST_RESIZE;
+ verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+ // Soft input was hidden by default, so it doesn't need to call
+ // {@code IMS#hideSoftInput()}.
+ verifyHideSoftInput(hideSoftInput /* setNotVisible */, false /* hideSoftInput */);
+ break;
+ case SOFT_INPUT_STATE_VISIBLE:
+ case SOFT_INPUT_STATE_HIDDEN:
+ case SOFT_INPUT_STATE_UNCHANGED: // Do nothing
+ verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+ verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+ break;
+ case SOFT_INPUT_STATE_ALWAYS_VISIBLE:
+ verifyShowSoftInput(true /* setVisible */, true /* showSoftInput */);
+ verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+ break;
+ case SOFT_INPUT_STATE_ALWAYS_HIDDEN:
+ verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+ // Soft input was hidden by default, so it doesn't need to call
+ // {@code IMS#hideSoftInput()}.
+ verifyHideSoftInput(true /* setNotVisible */, false /* hideSoftInput */);
+ break;
+ default:
+ throw new IllegalStateException(
+ "Unhandled soft input mode: "
+ + InputMethodDebug.softInputModeToString(mSoftInputState));
+ }
+ }
+
+ @Test
+ public void startInputOrWindowGainedFocus_userNotRunning() throws RemoteException {
+ when(mMockUserManagerInternal.isUserRunning(anyInt())).thenReturn(false);
+
+ assertThat(
+ startInputOrWindowGainedFocus(
+ DEFAULT_SOFT_INPUT_FLAG, true /* forwardNavigation */))
+ .isEqualTo(InputBindResult.INVALID_USER);
+ verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+ verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+ }
+
+ @Test
+ public void startInputOrWindowGainedFocus_invalidFocusStatus() throws RemoteException {
+ int[] invalidImeClientFocus =
+ new int[] {
+ WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW,
+ WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH,
+ WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID
+ };
+ InputBindResult[] inputBingResult =
+ new InputBindResult[] {
+ InputBindResult.NOT_IME_TARGET_WINDOW,
+ InputBindResult.DISPLAY_ID_MISMATCH,
+ InputBindResult.INVALID_DISPLAY_ID
+ };
+
+ for (int i = 0; i < invalidImeClientFocus.length; i++) {
+ when(mMockWindowManagerInternal.hasInputMethodClientFocus(
+ any(), anyInt(), anyInt(), anyInt()))
+ .thenReturn(invalidImeClientFocus[i]);
+
+ assertThat(
+ startInputOrWindowGainedFocus(
+ DEFAULT_SOFT_INPUT_FLAG, true /* forwardNavigation */))
+ .isEqualTo(inputBingResult[i]);
+ verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+ verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+ }
+ }
+
+ private InputBindResult startInputOrWindowGainedFocus(
+ int startInputFlag, boolean forwardNavigation) {
+ int softInputMode = mSoftInputState | mSoftInputAdjustment;
+ if (forwardNavigation) {
+ softInputMode |= SOFT_INPUT_IS_FORWARD_NAVIGATION;
+ }
+
+ Log.i(
+ TAG,
+ "startInputOrWindowGainedFocus() softInputStateFlag="
+ + InputMethodDebug.softInputModeToString(mSoftInputState)
+ + ", softInputAdjustFlag="
+ + InputMethodDebug.softInputModeToString(mSoftInputAdjustment));
+
+ return mInputMethodManagerService.startInputOrWindowGainedFocus(
+ StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */,
+ mMockInputMethodClient /* client */,
+ mWindowToken /* windowToken */,
+ startInputFlag /* startInputFlags */,
+ softInputMode /* softInputMode */,
+ 0 /* windowFlags */,
+ mEditorInfo /* editorInfo */,
+ mMockRemoteInputConnection /* inputConnection */,
+ mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */,
+ mTargetSdkVersion /* unverifiedTargetSdkVersion */,
+ mCallingUserId /* userId */,
+ mMockImeOnBackInvokedDispatcher /* imeDispatcher */);
+ }
+
+ private void mockHasImeFocusAndRestoreImeVisibility(boolean restoreImeVisibility) {
+ when(mMockWindowManagerInternal.hasInputMethodClientFocus(
+ any(), anyInt(), anyInt(), anyInt()))
+ .thenReturn(WindowManagerInternal.ImeClientFocusResult.HAS_IME_FOCUS);
+ when(mMockWindowManagerInternal.shouldRestoreImeVisibility(any()))
+ .thenReturn(restoreImeVisibility);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index cb14864876ab..2583f44787ad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -325,7 +325,7 @@ public class DeviceIdleControllerTest {
doNothing().when(mAlarmManager).set(anyInt(), anyLong(), anyString(), any(), any());
doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(), any(), any());
doNothing().when(mAlarmManager)
- .setWindow(anyInt(), anyLong(), anyLong(), anyString(), any(), any());
+ .setWindow(anyInt(), anyLong(), anyLong(), anyString(), any(), any(Handler.class));
doReturn(mock(Sensor.class)).when(mSensorManager)
.getDefaultSensor(eq(Sensor.TYPE_SIGNIFICANT_MOTION), eq(true));
doReturn(true).when(mSensorManager).registerListener(any(), any(), anyInt());
@@ -1111,12 +1111,12 @@ public class DeviceIdleControllerTest {
alarmManagerInOrder.verify(mAlarmManager).setWindow(
eq(AlarmManager.ELAPSED_REALTIME),
eq(idleAfterInactiveExpiryTime),
- anyLong(), anyString(), any(), any());
+ anyLong(), anyString(), any(), any(Handler.class));
// Maintenance alarm
alarmManagerInOrder.verify(mAlarmManager).setWindow(
eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
eq(idleAfterInactiveExpiryTime + idlingTimeMs),
- anyLong(), anyString(), any(), any());
+ anyLong(), anyString(), any(), any(Handler.class));
final AlarmManager.OnAlarmListener progressionListener =
alarmListenerCaptor.getAllValues().get(0);
@@ -1130,7 +1130,7 @@ public class DeviceIdleControllerTest {
alarmManagerInOrder.verify(mAlarmManager).setWindow(
eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
eq(mInjector.nowElapsed + idlingTimeMs),
- anyLong(), anyString(), any(), any());
+ anyLong(), anyString(), any(), any(Handler.class));
for (int i = 0; i < 2; ++i) {
// IDLE->MAINTENANCE alarm
@@ -1144,12 +1144,12 @@ public class DeviceIdleControllerTest {
alarmManagerInOrder.verify(mAlarmManager).setWindow(
eq(AlarmManager.ELAPSED_REALTIME),
eq(maintenanceExpiryTime),
- anyLong(), anyString(), any(), any());
+ anyLong(), anyString(), any(), any(Handler.class));
// Set IDLE->MAINTENANCE
alarmManagerInOrder.verify(mAlarmManager).setWindow(
eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
eq(maintenanceExpiryTime + idlingTimeMs),
- anyLong(), anyString(), any(), any());
+ anyLong(), anyString(), any(), any(Handler.class));
// MAINTENANCE->IDLE alarm
mInjector.nowElapsed = mDeviceIdleController.getNextLightAlarmTimeForTesting();
@@ -1159,7 +1159,7 @@ public class DeviceIdleControllerTest {
alarmManagerInOrder.verify(mAlarmManager).setWindow(
eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
eq(mInjector.nowElapsed + idlingTimeMs),
- anyLong(), anyString(), any(), any());
+ anyLong(), anyString(), any(), any(Handler.class));
}
}
@@ -2019,7 +2019,8 @@ public class DeviceIdleControllerTest {
final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListener = ArgumentCaptor
.forClass(AlarmManager.OnAlarmListener.class);
doNothing().when(mAlarmManager).setWindow(
- anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion"), any(), any());
+ anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion"), any(),
+ any(Handler.class));
doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(),
eq("DeviceIdleController.motion_registration"),
alarmListener.capture(), any());
@@ -2063,7 +2064,8 @@ public class DeviceIdleControllerTest {
final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListener = ArgumentCaptor
.forClass(AlarmManager.OnAlarmListener.class);
doNothing().when(mAlarmManager).setWindow(
- anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion"), any(), any());
+ anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion"), any(),
+ any(Handler.class));
doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(),
eq("DeviceIdleController.motion_registration"),
alarmListener.capture(), any());
@@ -2130,7 +2132,7 @@ public class DeviceIdleControllerTest {
eq(SensorManager.SENSOR_DELAY_NORMAL));
inOrder.verify(mAlarmManager).setWindow(
anyInt(), eq(mInjector.nowElapsed + mConstants.MOTION_INACTIVE_TIMEOUT), anyLong(),
- eq("DeviceIdleController.motion"), any(), any());
+ eq("DeviceIdleController.motion"), any(), any(Handler.class));
final SensorEventListener listener = listenerCaptor.getValue();
// Trigger motion
@@ -2140,7 +2142,7 @@ public class DeviceIdleControllerTest {
final ArgumentCaptor<Long> registrationTimeCaptor = ArgumentCaptor.forClass(Long.class);
inOrder.verify(mAlarmManager).setWindow(
anyInt(), registrationTimeCaptor.capture(), anyLong(),
- eq("DeviceIdleController.motion_registration"), any(), any());
+ eq("DeviceIdleController.motion_registration"), any(), any(Handler.class));
// Make sure the listener is re-registered.
mInjector.nowElapsed = registrationTimeCaptor.getValue();
@@ -2150,7 +2152,7 @@ public class DeviceIdleControllerTest {
eq(SensorManager.SENSOR_DELAY_NORMAL));
final ArgumentCaptor<Long> timeoutCaptor = ArgumentCaptor.forClass(Long.class);
inOrder.verify(mAlarmManager).setWindow(anyInt(), timeoutCaptor.capture(), anyLong(),
- eq("DeviceIdleController.motion"), any(), any());
+ eq("DeviceIdleController.motion"), any(), any(Handler.class));
// No motion before timeout
stationaryListener.motionExpected = false;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index e1a4c1dd7256..de5960363fa5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -280,13 +280,13 @@ public class BroadcastQueueTest {
constants.TIMEOUT = 100;
constants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
final BroadcastSkipPolicy emptySkipPolicy = new BroadcastSkipPolicy(mAms) {
- public boolean shouldSkip(BroadcastRecord r, ResolveInfo info) {
+ public boolean shouldSkip(BroadcastRecord r, Object o) {
// Ignored
return false;
}
- public boolean shouldSkip(BroadcastRecord r, BroadcastFilter filter) {
+ public String shouldSkipMessage(BroadcastRecord r, Object o) {
// Ignored
- return false;
+ return null;
}
};
final BroadcastHistory emptyHistory = new BroadcastHistory(constants) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 1753fc7a61e4..fc737d06059d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -41,6 +41,7 @@ import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
import android.app.UiModeManager;
import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
@@ -235,6 +236,59 @@ public class JobSchedulerServiceTest {
mService.getMinJobExecutionGuaranteeMs(jobDef));
}
+
+ /**
+ * Confirm that {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int)}
+ * returns a job with the correct delay and deadline constraints.
+ */
+ @Test
+ public void testGetRescheduleJobForFailure() {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ final long initialBackoffMs = MINUTE_IN_MILLIS;
+ mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO = 3;
+
+ JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure",
+ createJobInfo()
+ .setBackoffCriteria(initialBackoffMs, JobInfo.BACKOFF_POLICY_LINEAR));
+ assertEquals(JobStatus.NO_EARLIEST_RUNTIME, originalJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, originalJob.getLatestRunTimeElapsed());
+
+ // failure = 0, systemStop = 1
+ JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
+ JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
+ assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 0, systemStop = 2
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.INTERNAL_STOP_REASON_PREEMPT);
+ // failure = 0, systemStop = 3
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED);
+ assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 0, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO
+ for (int i = 0; i < mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO; ++i) {
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.INTERNAL_STOP_REASON_RTC_UPDATED);
+ }
+ assertEquals(nowElapsed + 2 * initialBackoffMs, rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 1, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ assertEquals(nowElapsed + 3 * initialBackoffMs, rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 2, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
+ assertEquals(nowElapsed + 4 * initialBackoffMs, rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+ }
+
/**
* Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
* with the correct delay and deadline constraints if the periodic job is scheduled with the
@@ -544,14 +598,16 @@ public class JobSchedulerServiceTest {
final long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow_failedJob",
createJobInfo().setPeriodic(HOUR_IN_MILLIS));
- JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job);
+ JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
+ JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 5 minutes
- failedJob = mService.getRescheduleJobForFailureLocked(job);
+ failedJob = mService.getRescheduleJobForFailureLocked(job,
+ JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 10 minutes
rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
@@ -559,7 +615,8 @@ public class JobSchedulerServiceTest {
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
advanceElapsedClock(35 * MINUTE_IN_MILLIS); // now + 45 minutes
- failedJob = mService.getRescheduleJobForFailureLocked(job);
+ failedJob = mService.getRescheduleJobForFailureLocked(job,
+ JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 55 minutes
rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
@@ -569,7 +626,8 @@ public class JobSchedulerServiceTest {
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 57 minutes
- failedJob = mService.getRescheduleJobForFailureLocked(job);
+ failedJob = mService.getRescheduleJobForFailureLocked(job,
+ JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 59 minutes
rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
@@ -665,7 +723,8 @@ public class JobSchedulerServiceTest {
public void testGetRescheduleJobForPeriodic_outsideWindow_failedJob() {
JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow_failedJob",
createJobInfo().setPeriodic(HOUR_IN_MILLIS));
- JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job);
+ JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
+ JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
long now = sElapsedRealtimeClock.millis();
long nextWindowStartTime = now + HOUR_IN_MILLIS;
long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
@@ -701,7 +760,8 @@ public class JobSchedulerServiceTest {
JobStatus job = createJobStatus(
"testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob",
createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS));
- JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job);
+ JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
+ JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
// First window starts 30 minutes from now.
advanceElapsedClock(30 * MINUTE_IN_MILLIS);
long now = sElapsedRealtimeClock.millis();
@@ -742,7 +802,8 @@ public class JobSchedulerServiceTest {
JobStatus job = createJobStatus(
"testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob_longPeriod",
createJobInfo().setPeriodic(7 * DAY_IN_MILLIS, 9 * HOUR_IN_MILLIS));
- JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job);
+ JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
+ JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
// First window starts 6.625 days from now.
advanceElapsedClock(6 * DAY_IN_MILLIS + 15 * HOUR_IN_MILLIS);
long now = sElapsedRealtimeClock.millis();
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 674e500d2e41..3bee6871b4b9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -489,19 +489,22 @@ public class FlexibilityControllerTest {
JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000L);
JobStatus js = createJobStatus("time", jb);
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, FROZEN_TIME, FROZEN_TIME);
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 0,
+ FROZEN_TIME, FROZEN_TIME);
assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 3, FROZEN_TIME, FROZEN_TIME);
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 1,
+ FROZEN_TIME, FROZEN_TIME);
assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 10, FROZEN_TIME, FROZEN_TIME);
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 10,
+ FROZEN_TIME, FROZEN_TIME);
assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
}
@@ -637,7 +640,12 @@ public class FlexibilityControllerTest {
JobInfo.Builder jb = createJob(0);
JobStatus js = createJobStatus("time", jb);
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1, FROZEN_TIME, FROZEN_TIME);
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1, /* numSystemStops */ 0,
+ FROZEN_TIME, FROZEN_TIME);
+ assertFalse(js.hasFlexibilityConstraint());
+ js = new JobStatus(
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 1,
+ FROZEN_TIME, FROZEN_TIME);
assertFalse(js.hasFlexibilityConstraint());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 149ae0b81740..7f522b0a1af5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -248,20 +248,32 @@ public class JobStatusTest {
// Less than 2 failures, priority shouldn't be affected.
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
- int backoffAttempt = 1;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ int numFailures = 1;
+ int numSystemStops = 0;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// 2+ failures, priority should be lowered as much as possible.
- backoffAttempt = 2;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ numFailures = 2;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
- backoffAttempt = 5;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ numFailures = 5;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
- backoffAttempt = 8;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ numFailures = 8;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
+
+ // System stops shouldn't factor in the downgrade.
+ numSystemStops = 10;
+ numFailures = 0;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
+ assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
}
@Test
@@ -274,33 +286,48 @@ public class JobStatusTest {
// Less than 2 failures, priority shouldn't be affected.
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
- int backoffAttempt = 1;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ int numFailures = 1;
+ int numSystemStops = 0;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
// Failures in [2,4), priority should be lowered slightly.
- backoffAttempt = 2;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ numFailures = 2;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
- backoffAttempt = 3;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ numFailures = 3;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
// Failures in [4,6), priority should be lowered more.
- backoffAttempt = 4;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ numFailures = 4;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
- backoffAttempt = 5;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ numFailures = 5;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
// 6+ failures, priority should be lowered as much as possible.
- backoffAttempt = 6;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ numFailures = 6;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
- backoffAttempt = 12;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ numFailures = 12;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
+
+ // System stops shouldn't factor in the downgrade.
+ numSystemStops = 10;
+ numFailures = 0;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
+ assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
}
/**
@@ -317,23 +344,36 @@ public class JobStatusTest {
// Less than 6 failures, priority shouldn't be affected.
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
- int backoffAttempt = 1;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ int numFailures = 1;
+ int numSystemStops = 0;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
- backoffAttempt = 4;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ numFailures = 4;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
- backoffAttempt = 5;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ numFailures = 5;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
// 6+ failures, priority should be lowered as much as possible.
- backoffAttempt = 6;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ numFailures = 6;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
- backoffAttempt = 12;
- job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+ numFailures = 12;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
+
+ // System stops shouldn't factor in the downgrade.
+ numSystemStops = 10;
+ numFailures = 0;
+ job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+ numSystemStops, 0, 0);
+ assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
}
/**
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index bb477b18d5fa..b949b3b265af 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -47,6 +47,7 @@ import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedLis
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
+import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.SystemClock;
@@ -276,13 +277,13 @@ public class PrefetchControllerTest {
inOrder.verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
.setWindow(
anyInt(), eq(sElapsedRealtimeClock.millis() + 4 * HOUR_IN_MILLIS),
- anyLong(), eq(TAG_PREFETCH), any(), any());
+ anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class));
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 3 * HOUR_IN_MILLIS);
inOrder.verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
.setWindow(
anyInt(), eq(sElapsedRealtimeClock.millis() + 8 * HOUR_IN_MILLIS),
- anyLong(), eq(TAG_PREFETCH), any(), any());
+ anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class));
}
@Test
@@ -414,7 +415,7 @@ public class PrefetchControllerTest {
verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
.setWindow(
anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS),
- anyLong(), eq(TAG_PREFETCH), any(), any());
+ anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class));
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertFalse(jobStatus.isReady());
}
@@ -464,7 +465,7 @@ public class PrefetchControllerTest {
verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
.setWindow(
anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS),
- anyLong(), eq(TAG_PREFETCH), any(), any());
+ anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class));
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertFalse(jobStatus.isReady());
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 9407968dbb56..bc7757b434ea 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -2708,7 +2708,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
// Test with timing sessions out of window but still under max execution limit.
@@ -2725,7 +2725,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1), false);
@@ -2734,7 +2734,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobStatus);
@@ -2749,7 +2749,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
}
@Test
@@ -2771,7 +2772,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test with timing sessions out of window.
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -2782,7 +2783,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test with timing sessions in window but still in quota.
final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
@@ -2796,7 +2797,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Add some more sessions, but still in quota.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2808,7 +2809,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test when out of quota.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2818,7 +2819,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
// Alarm already scheduled, so make sure it's not scheduled again.
synchronized (mQuotaController.mLock) {
@@ -2826,7 +2828,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
}
@Test
@@ -2850,7 +2853,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test with timing sessions out of window.
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -2861,7 +2864,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test with timing sessions in window but still in quota.
final long start = now - (6 * HOUR_IN_MILLIS);
@@ -2873,7 +2876,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Add some more sessions, but still in quota.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2885,7 +2888,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test when out of quota.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2895,7 +2898,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
// Alarm already scheduled, so make sure it's not scheduled again.
synchronized (mQuotaController.mLock) {
@@ -2903,7 +2907,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
}
/**
@@ -2932,7 +2937,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test with timing sessions out of window.
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -2943,7 +2948,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test with timing sessions in window but still in quota.
final long start = now - (6 * HOUR_IN_MILLIS);
@@ -2955,7 +2960,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Add some more sessions, but still in quota.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2967,7 +2972,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test when out of quota.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2977,7 +2982,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
// Alarm already scheduled, so make sure it's not scheduled again.
synchronized (mQuotaController.mLock) {
@@ -2985,7 +2991,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
}
@Test
@@ -3013,7 +3020,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test with timing sessions out of window.
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -3023,7 +3030,7 @@ public class QuotaControllerTest {
mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test with timing sessions in window but still in quota.
final long start = now - (6 * HOUR_IN_MILLIS);
@@ -3039,7 +3046,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Add some more sessions, but still in quota.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -3051,7 +3058,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test when out of quota.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -3061,7 +3068,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
// Alarm already scheduled, so make sure it's not scheduled again.
synchronized (mQuotaController.mLock) {
@@ -3069,7 +3077,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
}
/** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
@@ -3108,7 +3117,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(0))
- .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
inOrder.verify(mAlarmManager, timeout(1000).times(0))
.cancel(any(AlarmManager.OnAlarmListener.class));
@@ -3123,7 +3133,7 @@ public class QuotaControllerTest {
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
final long expectedFrequentAlarmTime =
outOfQuotaTime + (8 * HOUR_IN_MILLIS)
@@ -3135,7 +3145,7 @@ public class QuotaControllerTest {
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
final long expectedRareAlarmTime =
outOfQuotaTime + (24 * HOUR_IN_MILLIS)
@@ -3146,7 +3156,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
// And back up again.
setStandbyBucket(FREQUENT_INDEX, jobStatus);
@@ -3156,7 +3167,7 @@ public class QuotaControllerTest {
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
setStandbyBucket(WORKING_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
@@ -3165,7 +3176,7 @@ public class QuotaControllerTest {
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
setStandbyBucket(ACTIVE_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
@@ -3173,7 +3184,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(0))
- .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
inOrder.verify(mAlarmManager, timeout(1000).times(1))
.cancel(any(AlarmManager.OnAlarmListener.class));
}
@@ -3210,7 +3222,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Valid time in the future, so the count should be used.
stats.jobRateLimitExpirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS / 2;
@@ -3221,7 +3233,7 @@ public class QuotaControllerTest {
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
}
/**
@@ -3318,7 +3330,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
}
private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() {
@@ -3355,7 +3368,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
}
@Test
@@ -4611,7 +4625,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Ran jobs up to the job limit. All of them should be allowed to run.
for (int i = 0; i < mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
@@ -4630,7 +4644,7 @@ public class QuotaControllerTest {
}
// Start alarm shouldn't have been scheduled since the app was in quota up until this point.
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// The app is now out of job count quota
JobStatus throttledJob = createJobStatus(
@@ -4649,7 +4663,7 @@ public class QuotaControllerTest {
final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed;
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
}
/**
@@ -4680,7 +4694,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Ran jobs up to the job limit. All of them should be allowed to run.
for (int i = 0; i < mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
@@ -4701,7 +4715,7 @@ public class QuotaControllerTest {
}
// Start alarm shouldn't have been scheduled since the app was in quota up until this point.
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// The app is now out of session count quota
JobStatus throttledJob = createJobStatus(
@@ -4721,7 +4735,7 @@ public class QuotaControllerTest {
final long expectedWorkingAlarmTime = stats.sessionRateLimitExpirationTimeElapsed;
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
}
@Test
@@ -5185,7 +5199,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
inOrder.verify(mAlarmManager, timeout(1000).times(0))
- .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
// Test with timing sessions out of window.
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -5196,7 +5211,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
inOrder.verify(mAlarmManager, timeout(1000).times(0))
- .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
// Test with timing sessions in window but still in quota.
final long end = now - (22 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
@@ -5208,7 +5224,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
inOrder.verify(mAlarmManager, timeout(1000).times(0))
- .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
// Add some more sessions, but still in quota.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -5220,7 +5237,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
inOrder.verify(mAlarmManager, timeout(1000).times(0))
- .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
// Test when out of quota.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -5230,7 +5248,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
// Alarm already scheduled, so make sure it's not scheduled again.
synchronized (mQuotaController.mLock) {
@@ -5238,7 +5257,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
inOrder.verify(mAlarmManager, timeout(1000).times(0))
- .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
}
/** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
@@ -5282,7 +5302,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(0))
- .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
inOrder.verify(mAlarmManager, timeout(1000).times(0))
.cancel(any(AlarmManager.OnAlarmListener.class));
@@ -5297,7 +5318,7 @@ public class QuotaControllerTest {
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
setStandbyBucket(FREQUENT_INDEX);
final long expectedFrequentAlarmTime =
@@ -5308,7 +5329,7 @@ public class QuotaControllerTest {
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
setStandbyBucket(RARE_INDEX);
final long expectedRareAlarmTime =
@@ -5319,7 +5340,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
// And back up again.
setStandbyBucket(FREQUENT_INDEX);
@@ -5329,7 +5351,7 @@ public class QuotaControllerTest {
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
setStandbyBucket(WORKING_INDEX);
synchronized (mQuotaController.mLock) {
@@ -5338,7 +5360,7 @@ public class QuotaControllerTest {
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
setStandbyBucket(ACTIVE_INDEX);
synchronized (mQuotaController.mLock) {
@@ -5346,7 +5368,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(0))
- .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
inOrder.verify(mAlarmManager, timeout(1000).times(1))
.cancel(any(AlarmManager.OnAlarmListener.class));
}
@@ -5388,7 +5411,8 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
}
/** Tests that TimingSessions aren't saved when the device is charging. */
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
index 47f449c7f897..1be7e2e6e30e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
@@ -223,7 +223,7 @@ public final class BackgroundDexOptServiceUnitTest {
/* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
/* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
runFullJob(mJobServiceForIdle, mJobParametersForIdle,
- /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK,
+ /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
/* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
}
@@ -241,7 +241,7 @@ public final class BackgroundDexOptServiceUnitTest {
assertThat(getFailedPackageNamesSecondary()).isEmpty();
runFullJob(mJobServiceForIdle, mJobParametersForIdle,
- /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK,
+ /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
/* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA);
assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA);
@@ -256,7 +256,7 @@ public final class BackgroundDexOptServiceUnitTest {
mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_PERFORMED;
runFullJob(mJobServiceForIdle, mJobParametersForIdle,
- /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK,
+ /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
/* totalJobFinishedWithParams= */ 2, /* expectedSkippedPackage= */ null);
assertThat(getFailedPackageNamesPrimary()).isEmpty();
@@ -393,7 +393,7 @@ public final class BackgroundDexOptServiceUnitTest {
mCancelThread.join(TEST_WAIT_TIMEOUT_MS);
// Always reschedule for periodic job
- verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, true);
+ verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, false);
verifyLastControlDexOptBlockingCall(false);
}
@@ -421,7 +421,7 @@ public final class BackgroundDexOptServiceUnitTest {
mCancelThread.join(TEST_WAIT_TIMEOUT_MS);
// Always reschedule for periodic job
- verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, true);
+ verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, false);
verify(mDexOptHelper, never()).controlDexOptBlocking(true);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
index 2fac31e9e6fd..d477cb6f356c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
@@ -73,7 +73,12 @@ public class AgentTrendCalculatorTest {
}
@Override
- long getHardSatiatedConsumptionLimit() {
+ long getMinSatiatedConsumptionLimit() {
+ return 0;
+ }
+
+ @Override
+ long getMaxSatiatedConsumptionLimit() {
return 0;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
index fb3e8f298424..84a61c7a21e5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
@@ -132,8 +132,10 @@ public class AlarmManagerEconomicPolicyTest {
public void testDefaults() {
assertEquals(EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
mEconomicPolicy.getInitialSatiatedConsumptionLimit());
- assertEquals(EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
- mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getMinSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getMaxSatiatedConsumptionLimit());
final String pkgRestricted = "com.pkg.restricted";
when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -150,13 +152,15 @@ public class AlarmManagerEconomicPolicyTest {
@Test
public void testConstantsUpdating_ValidValues() {
setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
- setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(25));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT, arcToCake(3));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(25));
setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(10));
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(9));
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(7));
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
- assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(arcToCake(3), mEconomicPolicy.getMinSatiatedConsumptionLimit());
+ assertEquals(arcToCake(25), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
final String pkgRestricted = "com.pkg.restricted";
when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -171,13 +175,15 @@ public class AlarmManagerEconomicPolicyTest {
public void testConstantsUpdating_InvalidValues() {
// Test negatives.
setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(-5));
- setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(-5));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT, arcToCake(-5));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(-5));
setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(-1));
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2));
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3));
assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
- assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(arcToCake(1), mEconomicPolicy.getMinSatiatedConsumptionLimit());
+ assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
final String pkgRestricted = "com.pkg.restricted";
when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -188,14 +194,16 @@ public class AlarmManagerEconomicPolicyTest {
assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
// Test min+max reversed.
- setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
- setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(3));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT, arcToCake(5));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(4));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(3));
setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(10));
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(11));
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(13));
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
- assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedConsumptionLimit());
+ assertEquals(arcToCake(5), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
index 6da4ab714d7a..cad608f8ff59 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
@@ -155,9 +155,12 @@ public class CompleteEconomicPolicyTest {
assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES
+ EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
mEconomicPolicy.getInitialSatiatedConsumptionLimit());
- assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES
- + EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
- mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES
+ + EconomyManager.DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getMinSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES
+ + EconomyManager.DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getMaxSatiatedConsumptionLimit());
final String pkgRestricted = "com.pkg.restricted";
when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -178,8 +181,10 @@ public class CompleteEconomicPolicyTest {
public void testConstantsUpdated() {
setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(4));
setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(6));
- setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(24));
- setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(26));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(2));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT, arcToCake(3));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(24));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(26));
setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(9));
setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(11));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(8));
@@ -188,7 +193,8 @@ public class CompleteEconomicPolicyTest {
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(2));
assertEquals(arcToCake(10), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
- assertEquals(arcToCake(50), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedConsumptionLimit());
+ assertEquals(arcToCake(50), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
final String pkgRestricted = "com.pkg.restricted";
when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -206,8 +212,10 @@ public class CompleteEconomicPolicyTest {
setDeviceConfigBoolean(EconomyManager.KEY_ENABLE_POLICY_JOB_SCHEDULER, false);
assertEquals(EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
mEconomicPolicy.getInitialSatiatedConsumptionLimit());
- assertEquals(EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
- mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getMinSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getMaxSatiatedConsumptionLimit());
final String pkgRestricted = "com.pkg.restricted";
when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -229,8 +237,10 @@ public class CompleteEconomicPolicyTest {
setDeviceConfigBoolean(EconomyManager.KEY_ENABLE_POLICY_JOB_SCHEDULER, true);
assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES,
mEconomicPolicy.getInitialSatiatedConsumptionLimit());
- assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES,
- mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getMinSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getMaxSatiatedConsumptionLimit());
when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
index b7bbcd7562cd..ebf760cdf857 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
@@ -132,8 +132,10 @@ public class JobSchedulerEconomicPolicyTest {
public void testDefaults() {
assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES,
mEconomicPolicy.getInitialSatiatedConsumptionLimit());
- assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES,
- mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getMinSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getMaxSatiatedConsumptionLimit());
final String pkgRestricted = "com.pkg.restricted";
when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
@@ -171,7 +173,8 @@ public class JobSchedulerEconomicPolicyTest {
@Test
public void testConstantsUpdating_ValidValues() {
setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
- setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(25));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(2));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(25));
setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(6));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(4));
@@ -179,7 +182,8 @@ public class JobSchedulerEconomicPolicyTest {
arcToCake(1));
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
- assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(arcToCake(2), mEconomicPolicy.getMinSatiatedConsumptionLimit());
+ assertEquals(arcToCake(25), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
final String pkgRestricted = "com.pkg.restricted";
when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -198,7 +202,8 @@ public class JobSchedulerEconomicPolicyTest {
public void testConstantsUpdating_InvalidValues() {
// Test negatives.
setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(-5));
- setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(-5));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(-5));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(-5));
setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(-1));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3));
@@ -206,7 +211,8 @@ public class JobSchedulerEconomicPolicyTest {
arcToCake(-4));
assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
- assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(arcToCake(1), mEconomicPolicy.getMinSatiatedConsumptionLimit());
+ assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
final String pkgRestricted = "com.pkg.restricted";
when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -221,14 +227,16 @@ public class JobSchedulerEconomicPolicyTest {
mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
// Test min+max reversed.
- setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
- setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(3));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(5));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(4));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(3));
setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(11));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(13));
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
- assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedConsumptionLimit());
+ assertEquals(arcToCake(5), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
index 00d7541a79dc..a3a49d7035d9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.when;
import android.app.AlarmManager;
import android.content.Context;
+import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.ArraySet;
@@ -222,7 +223,8 @@ public class AlarmQueueTest {
alarmQueue.addAlarm("com.android.test.1", nowElapsed + HOUR_IN_MILLIS);
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), anyLong(), eq(ALARM_TAG), any(), any());
+ anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), anyLong(), eq(ALARM_TAG), any(), any(
+ Handler.class));
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
index 608b64e7d12a..0d14c9f02677 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
@@ -589,14 +589,14 @@ public class CountQuotaTrackerTest {
// No sessions saved yet.
mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
verify(mAlarmManager, never()).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test with timing sessions out of window.
final long now = mInjector.getElapsedRealtime();
logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 10 * HOUR_IN_MILLIS, 20);
mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
verify(mAlarmManager, never()).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test with timing sessions in window but still in quota.
final long start = now - (6 * HOUR_IN_MILLIS);
@@ -604,25 +604,27 @@ public class CountQuotaTrackerTest {
logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, start, 5);
mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
verify(mAlarmManager, never()).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Add some more sessions, but still in quota.
logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS, 1);
logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 3);
mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
verify(mAlarmManager, never()).setWindow(
- anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// Test when out of quota.
logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 1);
mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
// Alarm already scheduled, so make sure it's not scheduled again.
mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
verify(mAlarmManager, times(1)).setWindow(
- anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
}
/** Tests that the start alarm is properly rescheduled if the app's category is changed. */
@@ -656,7 +658,8 @@ public class CountQuotaTrackerTest {
mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
inOrder.verify(mAlarmManager, never())
- .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
// And down from there.
@@ -665,41 +668,42 @@ public class CountQuotaTrackerTest {
mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
inOrder.verify(mAlarmManager, timeout(1000).times(1))
.setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
final long expectedFrequentAlarmTime = outOfQuotaTime + (8 * HOUR_IN_MILLIS);
mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
inOrder.verify(mAlarmManager, timeout(1000).times(1))
.setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
final long expectedRareAlarmTime = outOfQuotaTime + (24 * HOUR_IN_MILLIS);
mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
inOrder.verify(mAlarmManager, timeout(1000).times(1))
.setWindow(anyInt(), eq(expectedRareAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
// And back up again.
mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
inOrder.verify(mAlarmManager, timeout(1000).times(1))
.setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
inOrder.verify(mAlarmManager, timeout(1000).times(1))
.setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
- eq(TAG_QUOTA_CHECK), any(), any());
+ eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
inOrder.verify(mAlarmManager, timeout(1000).times(1))
.cancel(any(AlarmManager.OnAlarmListener.class));
inOrder.verify(mAlarmManager, timeout(1000).times(0))
- .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+ any(Handler.class));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
index c321639ffe2e..1a8ef9e53593 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
@@ -387,7 +387,7 @@ public class AppsFilterImplTest {
// delete user
when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST);
- appsFilter.onUserDeleted(ADDED_USER);
+ appsFilter.onUserDeleted(mSnapshot, ADDED_USER);
for (int subjectUserId : USER_ARRAY) {
for (int otherUserId : USER_ARRAY) {
@@ -925,7 +925,7 @@ public class AppsFilterImplTest {
assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_OVERLAY_APPID,
overlaySetting, actorSetting, SYSTEM_USER));
- appsFilter.removePackage(mSnapshot, targetSetting, false /* isReplace */);
+ appsFilter.removePackage(mSnapshot, targetSetting);
// Actor loses visibility to the overlay via removal of the target
assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
@@ -1267,7 +1267,7 @@ public class AppsFilterImplTest {
watcher.verifyNoChangeReported("get");
// remove a package
- appsFilter.removePackage(mSnapshot, seesNothing, false /* isReplace */);
+ appsFilter.removePackage(mSnapshot, seesNothing);
watcher.verifyChangeReported("removePackage");
}
@@ -1337,7 +1337,7 @@ public class AppsFilterImplTest {
target.getPackageName()));
// New changes don't affect the snapshot
- appsFilter.removePackage(mSnapshot, target, false);
+ appsFilter.removePackage(mSnapshot, target);
assertTrue(
appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
target,
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d314a6579b58..2c7867cfd1a2 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1147,8 +1147,12 @@ public class CarrierConfigManager {
"carrier_data_call_apn_retry_after_disconnect_long";
/**
- * Data call setup permanent failure causes by the carrier
+ * Data call setup permanent failure causes by the carrier.
+ *
+ * @deprecated This API key was added in mistake and is not used anymore by the telephony data
+ * frameworks.
*/
+ @Deprecated
public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS =
"carrier_data_call_permanent_failure_strings";
@@ -2103,6 +2107,16 @@ public class CarrierConfigManager {
* is immediately closed (disabling keep-alive).
*/
public static final String KEY_MMS_CLOSE_CONNECTION_BOOL = "mmsCloseConnection";
+ /**
+ * Waiting time in milliseconds used before releasing an MMS data call. Not tearing down an MMS
+ * data connection immediately helps to reduce the message delivering latency if messaging
+ * continues between all parties in the conversation since the same data connection can be
+ * reused for further messages.
+ *
+ * This timer will control how long the data call will be kept alive before being torn down.
+ */
+ public static final String KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT =
+ "mms_network_release_timeout_millis_int";
/**
* The flatten {@link android.content.ComponentName componentName} of the activity that can
@@ -8436,7 +8450,8 @@ public class CarrierConfigManager {
*
* The syntax of the retry rule:
* 1. Retry based on {@link NetworkCapabilities}. Note that only APN-type network capabilities
- * are supported.
+ * are supported. If the capabilities are not specified, then the retry rule only applies
+ * to the current failed APN used in setup data call request.
* "capabilities=[netCaps1|netCaps2|...], [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
*
* 2. Retry based on {@link DataFailCause}
@@ -8447,15 +8462,16 @@ public class CarrierConfigManager {
* "capabilities=[netCaps1|netCaps2|...], fail_causes=[cause1|cause2|cause3|...],
* [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
*
+ * 4. Permanent fail causes (no timer-based retry) on the current failed APN. Retry interval
+ * is specified for retrying the next available APN.
+ * "permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|65543|65547|
+ * 2252|2253|2254, retry_interval=2500"
+ *
* For example,
* "capabilities=eims, retry_interval=1000, maximum_retries=20" means if the attached
* network request is emergency, then retry data network setup every 1 second for up to 20
* times.
*
- * "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|2254
- * , maximum_retries=0" means for those fail causes, never retry with timers. Note that
- * when environment changes, retry can still happen.
- *
* "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
* "5000|10000|15000|20000|40000|60000|120000|240000|600000|1200000|1800000"
* "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s,
@@ -9069,6 +9085,7 @@ public class CarrierConfigManager {
sDefaults.putInt(KEY_MMS_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_INT, -1);
sDefaults.putInt(KEY_MMS_SMS_TO_MMS_TEXT_THRESHOLD_INT, -1);
sDefaults.putInt(KEY_MMS_SUBJECT_MAX_LENGTH_INT, 40);
+ sDefaults.putInt(KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT, 5 * 1000);
sDefaults.putString(KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING, "");
sDefaults.putString(KEY_MMS_HTTP_PARAMS_STRING, "");
sDefaults.putString(KEY_MMS_NAI_SUFFIX_STRING, "");
@@ -9432,8 +9449,13 @@ public class CarrierConfigManager {
sDefaults.putStringArray(
KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY, new String[] {
"capabilities=eims, retry_interval=1000, maximum_retries=20",
- "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2252|"
- + "2253|2254, maximum_retries=0", // No retry for those causes
+ // Permanent fail causes. When setup data call fails with the following
+ // fail causes, telephony data frameworks will stop timer-based retry on
+ // the failed APN until power cycle, APM, or some special events. Note that
+ // even timer-based retry is not performed, condition-based (RAT changes,
+ // registration state changes) retry can still happen.
+ "permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|"
+ + "-3|65543|65547|2252|2253|2254, retry_interval=2500",
"capabilities=mms|supl|cbs, retry_interval=2000",
"capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
+ "5000|10000|15000|20000|40000|60000|120000|240000|"
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index d670e5592c42..1f301c1b2279 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2268,7 +2268,21 @@ public final class SmsManager {
RESULT_RIL_SIM_ABSENT,
RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED,
RESULT_RIL_ACCESS_BARRED,
- RESULT_RIL_BLOCKED_DUE_TO_CALL
+ RESULT_RIL_BLOCKED_DUE_TO_CALL,
+ RESULT_RIL_GENERIC_ERROR,
+ RESULT_RIL_INVALID_RESPONSE,
+ RESULT_RIL_SIM_PIN2,
+ RESULT_RIL_SIM_PUK2,
+ RESULT_RIL_SUBSCRIPTION_NOT_AVAILABLE,
+ RESULT_RIL_SIM_ERROR,
+ RESULT_RIL_INVALID_SIM_STATE,
+ RESULT_RIL_NO_SMS_TO_ACK,
+ RESULT_RIL_SIM_BUSY,
+ RESULT_RIL_SIM_FULL,
+ RESULT_RIL_NO_SUBSCRIPTION,
+ RESULT_RIL_NO_NETWORK_FOUND,
+ RESULT_RIL_DEVICE_IN_USE,
+ RESULT_RIL_ABORTED
})
@Retention(RetentionPolicy.SOURCE)
public @interface Result {}
@@ -2534,7 +2548,7 @@ public final class SmsManager {
public static final int RESULT_RIL_SIM_ABSENT = 120;
/**
- * 1X voice and SMS are not allowed simulteneously.
+ * 1X voice and SMS are not allowed simultaneously.
*/
public static final int RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED = 121;
@@ -2553,6 +2567,73 @@ public final class SmsManager {
*/
public static final int RESULT_RIL_GENERIC_ERROR = 124;
+ /**
+ * A RIL internal error when one of the RIL layers receives an unrecognized response from a
+ * lower layer.
+ */
+ public static final int RESULT_RIL_INVALID_RESPONSE = 125;
+
+ /**
+ * Operation requires SIM PIN2 to be entered
+ */
+ public static final int RESULT_RIL_SIM_PIN2 = 126;
+
+ /**
+ * Operation requires SIM PUK2 to be entered
+ */
+ public static final int RESULT_RIL_SIM_PUK2 = 127;
+
+ /**
+ * Fail to find CDMA subscription from specified location
+ */
+ public static final int RESULT_RIL_SUBSCRIPTION_NOT_AVAILABLE = 128;
+
+ /**
+ * Received error from SIM card
+ */
+ public static final int RESULT_RIL_SIM_ERROR = 129;
+
+ /**
+ * Cannot process the request in current SIM state
+ */
+ public static final int RESULT_RIL_INVALID_SIM_STATE = 130;
+
+ /**
+ * ACK received when there is no SMS to ack
+ */
+ public static final int RESULT_RIL_NO_SMS_TO_ACK = 131;
+
+ /**
+ * SIM is busy
+ */
+ public static final int RESULT_RIL_SIM_BUSY = 132;
+
+ /**
+ * The target EF is full
+ */
+ public static final int RESULT_RIL_SIM_FULL = 133;
+
+ /**
+ * Device does not have subscription
+ */
+ public static final int RESULT_RIL_NO_SUBSCRIPTION = 134;
+
+ /**
+ * Network cannot be found
+ */
+ public static final int RESULT_RIL_NO_NETWORK_FOUND = 135;
+
+ /**
+ * Operation cannot be performed because the device is currently in use
+ */
+ public static final int RESULT_RIL_DEVICE_IN_USE = 136;
+
+ /**
+ * Operation aborted
+ */
+ public static final int RESULT_RIL_ABORTED = 137;
+
+
// SMS receiving results sent as a "result" extra in {@link Intents.SMS_REJECTED_ACTION}
/**
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 98926710e053..9f612e6d7dd9 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -536,6 +536,12 @@ public interface RILConstants {
int RIL_REQUEST_TRIGGER_EMERGENCY_NETWORK_SCAN = 230;
int RIL_REQUEST_CANCEL_EMERGENCY_NETWORK_SCAN = 231;
int RIL_REQUEST_EXIT_EMERGENCY_MODE = 232;
+ int RIL_REQUEST_SET_SRVCC_CALL_INFO = 233;
+ int RIL_REQUEST_UPDATE_IMS_REGISTRATION_INFO = 234;
+ int RIL_REQUEST_START_IMS_TRAFFIC = 235;
+ int RIL_REQUEST_STOP_IMS_TRAFFIC = 236;
+ int RIL_REQUEST_SEND_ANBR_QUERY = 237;
+ int RIL_REQUEST_TRIGGER_EPS_FALLBACK = 238;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -607,4 +613,7 @@ public interface RILConstants {
int RIL_UNSOL_REGISTRATION_FAILED = 1104;
int RIL_UNSOL_BARRING_INFO_CHANGED = 1105;
int RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT = 1106;
+ int RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION = 1107;
+ int RIL_UNSOL_CONNECTION_SETUP_FAILURE = 1108;
+ int RIL_UNSOL_NOTIFY_ANBR = 1109;
}