diff options
278 files changed, 9280 insertions, 2526 deletions
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 72553ed7a0a5..90ce8bf27322 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -366,7 +366,7 @@ public final class JobServiceContext implements ServiceConnection { job.getNumFailures()); // Use the context's ID to distinguish traces since there'll only be one job running // per context. - Trace.asyncTraceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, job.getBatteryName(), getId()); + Trace.asyncTraceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, job.getTag(), getId()); try { mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid()); } catch (RemoteException e) { @@ -1030,7 +1030,7 @@ public final class JobServiceContext implements ServiceConnection { completedJob.getJob().getPriority(), completedJob.getEffectivePriority(), completedJob.getNumFailures()); - Trace.asyncTraceEnd(Trace.TRACE_TAG_SYSTEM_SERVER, completedJob.getBatteryName(), getId()); + Trace.asyncTraceEnd(Trace.TRACE_TAG_SYSTEM_SERVER, completedJob.getTag(), getId()); try { mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(), internalStopReason); diff --git a/api/Android.bp b/api/Android.bp index e9930e3a3b5b..29eaec6ffbd2 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -38,23 +38,9 @@ bootstrap_go_package { pluginFor: ["soong_build"], } -python_defaults { - name: "python3_version_defaults", - version: { - py2: { - enabled: false, - }, - py3: { - enabled: true, - embedded_launcher: false, - }, - }, -} - python_binary_host { name: "api_versions_trimmer", srcs: ["api_versions_trimmer.py"], - defaults: ["python3_version_defaults"], } python_test_host { @@ -64,7 +50,6 @@ python_test_host { "api_versions_trimmer_unittests.py", "api_versions_trimmer.py", ], - defaults: ["python3_version_defaults"], test_options: { unit_test: true, }, @@ -73,7 +58,6 @@ python_test_host { python_binary_host { name: "merge_annotation_zips", srcs: ["merge_annotation_zips.py"], - defaults: ["python3_version_defaults"], } python_test_host { @@ -83,7 +67,6 @@ python_test_host { "merge_annotation_zips.py", "merge_annotation_zips_test.py", ], - defaults: ["python3_version_defaults"], test_options: { unit_test: true, }, diff --git a/core/api/current.txt b/core/api/current.txt index 1bf0d0e23782..19713cfebf6c 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5933,6 +5933,7 @@ package android.app { field public static final String EXTRA_BIG_TEXT = "android.bigText"; field public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo"; field public static final String EXTRA_CALL_PERSON = "android.callPerson"; + field public static final String EXTRA_CALL_TYPE = "android.callType"; field public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID"; field public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID"; field public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; @@ -6240,6 +6241,10 @@ package android.app { method @NonNull public android.app.Notification.CallStyle setIsVideo(boolean); method @NonNull public android.app.Notification.CallStyle setVerificationIcon(@Nullable android.graphics.drawable.Icon); method @NonNull public android.app.Notification.CallStyle setVerificationText(@Nullable CharSequence); + field public static final int CALL_TYPE_INCOMING = 1; // 0x1 + field public static final int CALL_TYPE_ONGOING = 2; // 0x2 + field public static final int CALL_TYPE_SCREENING = 3; // 0x3 + field public static final int CALL_TYPE_UNKNOWN = 0; // 0x0 } public static final class Notification.CarExtender implements android.app.Notification.Extender { @@ -18919,8 +18924,8 @@ package android.inputmethodservice { method public android.view.View onCreateCandidatesView(); method public android.view.View onCreateExtractTextView(); method @Nullable public android.view.inputmethod.InlineSuggestionsRequest onCreateInlineSuggestionsRequest(@NonNull android.os.Bundle); - method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface(); - method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface(); + method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface(); + method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface(); method public android.view.View onCreateInputView(); method protected void onCurrentInputMethodSubtypeChanged(android.view.inputmethod.InputMethodSubtype); method public void onDisplayCompletions(android.view.inputmethod.CompletionInfo[]); @@ -41572,7 +41577,7 @@ package android.telephony { field public static final String KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL = "show_signal_strength_in_sim_status_bool"; field public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL = "show_video_call_charges_alert_dialog_bool"; field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool"; - field public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool"; + field @Deprecated public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool"; field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; field public static final String KEY_SMDP_SERVER_ADDRESS_STRING = "smdp_server_address_string"; field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 449900d82a49..7fd4283ca42e 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2804,6 +2804,7 @@ package android.companion.virtual { method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback); method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback); + method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualDpad createVirtualDpad(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); @@ -4373,6 +4374,11 @@ package android.hardware.hdmi { package android.hardware.input { + public class VirtualDpad implements java.io.Closeable { + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendKeyEvent(@NonNull android.hardware.input.VirtualKeyEvent); + } + public final class VirtualKeyEvent implements android.os.Parcelable { method public int describeContents(); method public int getAction(); @@ -10375,6 +10381,7 @@ package android.provider { field public static final String NAMESPACE_LOCATION = "location"; field public static final String NAMESPACE_MEDIA = "media"; field public static final String NAMESPACE_MEDIA_NATIVE = "media_native"; + field public static final String NAMESPACE_NEARBY = "nearby"; field public static final String NAMESPACE_NETD_NATIVE = "netd_native"; field public static final String NAMESPACE_NNAPI_NATIVE = "nnapi_native"; field public static final String NAMESPACE_ON_DEVICE_PERSONALIZATION = "on_device_personalization"; @@ -10404,6 +10411,7 @@ package android.provider { field public static final String NAMESPACE_TELEPHONY = "telephony"; field public static final String NAMESPACE_TETHERING = "tethering"; field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier"; + field public static final String NAMESPACE_TRANSPARENCY_METADATA = "transparency_metadata"; field public static final String NAMESPACE_UWB = "uwb"; field public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot"; } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 085bd5510da7..887af1f4f350 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1874,7 +1874,7 @@ package android.os { method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle); method public static boolean isSplitSystemUser(); - method public static boolean isUsersOnSecondaryDisplaysEnabled(); + method public boolean isUsersOnSecondaryDisplaysSupported(); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException; } @@ -2209,6 +2209,7 @@ package android.provider { field public static final String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE = "accessibility_shortcut_target_service"; field public static final String ANR_SHOW_BACKGROUND = "anr_show_background"; field public static final String AUTOFILL_SERVICE = "autofill_service"; + field public static final String BIOMETRIC_VIRTUAL_ENABLED = "biometric_virtual_enabled"; field public static final String CONTENT_CAPTURE_ENABLED = "content_capture_enabled"; field public static final String DISABLED_PRINT_SERVICES = "disabled_print_services"; field @Deprecated public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages"; diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index 0a906bee6fad..f9b8a3006880 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -873,6 +873,8 @@ NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_SHORTCUT_TARG New setting keys are not allowed (Field: ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#AUTOFILL_SERVICE: New setting keys are not allowed (Field: AUTOFILL_SERVICE); use getters/setters in relevant manager class +NoSettingsProvider: android.provider.Settings.Secure#BIOMETRIC_VIRTUAL_ENABLED: + New setting keys are not allowed (Field: BIOMETRIC_VIRTUAL_ENABLED); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#CONTENT_CAPTURE_ENABLED: New setting keys are not allowed (Field: CONTENT_CAPTURE_ENABLED); use getters/setters in relevant manager class NoSettingsProvider: android.provider.Settings.Secure#DISABLED_PRINT_SERVICES: @@ -1011,7 +1013,7 @@ UserHandleName: android.content.ContentCaptureOptions: Classes holding a set of parameters should be called `FooParams`, was `ContentCaptureOptions` -VisiblySynchronized: PsiThisExpression:this: +VisiblySynchronized: PsiThisExpression: Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.content.res.AssetManager.getApkPaths() VisiblySynchronized: android.content.res.AssetManager#getApkPaths(): Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.content.res.AssetManager.getApkPaths() diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index c2b315f7088d..212e3581269a 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -89,6 +89,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager.ServiceNotFoundException; import android.os.StrictMode; +import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.text.Selection; @@ -8304,13 +8305,15 @@ public class Activity extends ContextThemeWrapper mIsInPictureInPictureMode = windowingMode == WINDOWING_MODE_PINNED; mShouldDockBigOverlays = getResources().getBoolean(R.bool.config_dockBigOverlayWindows); restoreHasCurrentPermissionRequest(icicle); + final long startTime = SystemClock.uptimeMillis(); if (persistentState != null) { onCreate(icicle, persistentState); } else { onCreate(icicle); } + final long duration = SystemClock.uptimeMillis() - startTime; EventLogTags.writeWmOnCreateCalled(mIdent, getComponentName().getClassName(), - "performCreate"); + "performCreate", duration); mActivityTransitionState.readState(icicle); mVisibleFromClient = !mWindow.getWindowStyle().getBoolean( @@ -8332,8 +8335,11 @@ public class Activity extends ContextThemeWrapper mFragments.noteStateNotSaved(); mCalled = false; mFragments.execPendingActions(); + final long startTime = SystemClock.uptimeMillis(); mInstrumentation.callActivityOnStart(this); - EventLogTags.writeWmOnStartCalled(mIdent, getComponentName().getClassName(), reason); + final long duration = SystemClock.uptimeMillis() - startTime; + EventLogTags.writeWmOnStartCalled(mIdent, getComponentName().getClassName(), reason, + duration); if (!mCalled) { throw new SuperNotCalledException( @@ -8410,8 +8416,11 @@ public class Activity extends ContextThemeWrapper } mCalled = false; + final long startTime = SystemClock.uptimeMillis(); mInstrumentation.callActivityOnRestart(this); - EventLogTags.writeWmOnRestartCalled(mIdent, getComponentName().getClassName(), reason); + final long duration = SystemClock.uptimeMillis() - startTime; + EventLogTags.writeWmOnRestartCalled(mIdent, getComponentName().getClassName(), reason, + duration); if (!mCalled) { throw new SuperNotCalledException( "Activity " + mComponent.toShortString() + @@ -8438,9 +8447,12 @@ public class Activity extends ContextThemeWrapper getAutofillClientController().onActivityPerformResume(followedByPause); mCalled = false; + final long startTime = SystemClock.uptimeMillis(); // mResumed is set by the instrumentation mInstrumentation.callActivityOnResume(this); - EventLogTags.writeWmOnResumeCalled(mIdent, getComponentName().getClassName(), reason); + final long duration = SystemClock.uptimeMillis() - startTime; + EventLogTags.writeWmOnResumeCalled(mIdent, getComponentName().getClassName(), reason, + duration); if (!mCalled) { throw new SuperNotCalledException( "Activity " + mComponent.toShortString() + @@ -8483,9 +8495,11 @@ public class Activity extends ContextThemeWrapper mDoReportFullyDrawn = false; mFragments.dispatchPause(); mCalled = false; + final long startTime = SystemClock.uptimeMillis(); onPause(); + final long duration = SystemClock.uptimeMillis() - startTime; EventLogTags.writeWmOnPausedCalled(mIdent, getComponentName().getClassName(), - "performPause"); + "performPause", duration); mResumed = false; if (!mCalled && getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.GINGERBREAD) { @@ -8529,8 +8543,11 @@ public class Activity extends ContextThemeWrapper mFragments.dispatchStop(); mCalled = false; + final long startTime = SystemClock.uptimeMillis(); mInstrumentation.callActivityOnStop(this); - EventLogTags.writeWmOnStopCalled(mIdent, getComponentName().getClassName(), reason); + final long duration = SystemClock.uptimeMillis() - startTime; + EventLogTags.writeWmOnStopCalled(mIdent, getComponentName().getClassName(), reason, + duration); if (!mCalled) { throw new SuperNotCalledException( "Activity " + mComponent.toShortString() + @@ -8564,9 +8581,11 @@ public class Activity extends ContextThemeWrapper mDestroyed = true; mWindow.destroy(); mFragments.dispatchDestroy(); + final long startTime = SystemClock.uptimeMillis(); onDestroy(); + final long duration = SystemClock.uptimeMillis() - startTime; EventLogTags.writeWmOnDestroyCalled(mIdent, getComponentName().getClassName(), - "performDestroy"); + "performDestroy", duration); mFragments.doLoaderDestroy(); if (mVoiceInteractor != null) { mVoiceInteractor.detachActivity(); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 28404d5338c6..03c1e072092d 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1910,6 +1910,7 @@ public class AppOpsManager { OP_SCHEDULE_EXACT_ALARM, OP_MANAGE_MEDIA, OP_TURN_SCREEN_ON, + OP_GET_USAGE_STATS, }; static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{ diff --git a/core/java/android/app/EventLogTags.logtags b/core/java/android/app/EventLogTags.logtags index d0856f44d9a9..479f5a6722db 100644 --- a/core/java/android/app/EventLogTags.logtags +++ b/core/java/android/app/EventLogTags.logtags @@ -1,4 +1,4 @@ -# See system/core/logcat/event.logtags for a description of the format of this file. +# See system/logging/logcat/event.logtags for a description of the format of this file. option java_package android.app @@ -6,21 +6,21 @@ option java_package android.app # google3/googledata/wireless/android/provisioning/gservices.config !! # # The activity's onPause has been called. -30021 wm_on_paused_called (Token|1|5),(Component Name|3),(Reason|3) +30021 wm_on_paused_called (Token|1|5),(Component Name|3),(Reason|3),(time|2|3) # The activity's onResume has been called. -30022 wm_on_resume_called (Token|1|5),(Component Name|3),(Reason|3) +30022 wm_on_resume_called (Token|1|5),(Component Name|3),(Reason|3),(time|2|3) # The activity's onStop has been called. -30049 wm_on_stop_called (Token|1|5),(Component Name|3),(Reason|3) +30049 wm_on_stop_called (Token|1|5),(Component Name|3),(Reason|3),(time|2|3) # The activity's onCreate has been called. -30057 wm_on_create_called (Token|1|5),(Component Name|3),(Reason|3) +30057 wm_on_create_called (Token|1|5),(Component Name|3),(Reason|3),(time|2|3) # The activity's onRestart has been called. -30058 wm_on_restart_called (Token|1|5),(Component Name|3),(Reason|3) +30058 wm_on_restart_called (Token|1|5),(Component Name|3),(Reason|3),(time|2|3) # The activity's onStart has been called. -30059 wm_on_start_called (Token|1|5),(Component Name|3),(Reason|3) +30059 wm_on_start_called (Token|1|5),(Component Name|3),(Reason|3),(time|2|3) # The activity's onDestroy has been called. -30060 wm_on_destroy_called (Token|1|5),(Component Name|3),(Reason|3) +30060 wm_on_destroy_called (Token|1|5),(Component Name|3),(Reason|3),(time|2|3) # The activity's onActivityResult has been called. 30062 wm_on_activity_result_called (Token|1|5),(Component Name|3),(Reason|3) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 343adb82c8eb..9bbebc85255a 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1421,7 +1421,6 @@ public class Notification implements Parcelable /** * {@link #extras} key: the type of call represented by the * {@link android.app.Notification.CallStyle} notification. This extra is an int. - * @hide */ public static final String EXTRA_CALL_TYPE = "android.callType"; @@ -9301,11 +9300,43 @@ public class Notification implements Parcelable * </pre> */ public static class CallStyle extends Style { - /** @hide */ + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + CALL_TYPE_UNKNOWN, + CALL_TYPE_INCOMING, + CALL_TYPE_ONGOING, + CALL_TYPE_SCREENING + }) + public @interface CallType {}; + + /** + * Unknown call type. + * + * See {@link #EXTRA_CALL_TYPE}. + */ + public static final int CALL_TYPE_UNKNOWN = 0; + + /** + * Call type for incoming calls. + * + * See {@link #EXTRA_CALL_TYPE}. + */ public static final int CALL_TYPE_INCOMING = 1; - /** @hide */ + /** + * Call type for ongoing calls. + * + * See {@link #EXTRA_CALL_TYPE}. + */ public static final int CALL_TYPE_ONGOING = 2; - /** @hide */ + /** + * Call type for calls that are being screened. + * + * See {@link #EXTRA_CALL_TYPE}. + */ public static final int CALL_TYPE_SCREENING = 3; /** @@ -9392,13 +9423,14 @@ public class Notification implements Parcelable } /** + * @param callType The type of the call * @param person The person displayed for the incoming call. * The user also needs to have a non-empty name associated with it. * @param hangUpIntent The intent to be sent when the user taps the hang up action * @param declineIntent The intent to be sent when the user taps the decline action * @param answerIntent The intent to be sent when the user taps the answer action */ - private CallStyle(int callType, @NonNull Person person, + private CallStyle(@CallType int callType, @NonNull Person person, @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, @Nullable PendingIntent answerIntent) { if (person == null || TextUtils.isEmpty(person.getName())) { diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 8cfbf2faee67..9c99da52d962 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -57,6 +57,12 @@ interface IVirtualDevice { void onAudioSessionEnded(); + void createVirtualDpad( + int displayId, + String inputDeviceName, + int vendorId, + int productId, + IBinder token); void createVirtualKeyboard( int displayId, String inputDeviceName, @@ -77,6 +83,7 @@ interface IVirtualDevice { IBinder token, in Point screenSize); void unregisterInputDevice(IBinder token); + boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event); boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event); boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event); boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event); diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 1b93bb851567..d4c9a42c8115 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -37,6 +37,7 @@ import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplay; import android.hardware.display.VirtualDisplayConfig; +import android.hardware.input.VirtualDpad; import android.hardware.input.VirtualKeyboard; import android.hardware.input.VirtualMouse; import android.hardware.input.VirtualTouchscreen; @@ -315,6 +316,32 @@ public final class VirtualDeviceManager { } /** + * Creates a virtual dpad. + * + * @param display the display that the events inputted through this device should target + * @param inputDeviceName the name to call this input device + * @param vendorId the PCI vendor id + * @param productId the product id, as defined by the vendor + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @NonNull + public VirtualDpad createVirtualDpad( + @NonNull VirtualDisplay display, + @NonNull String inputDeviceName, + int vendorId, + int productId) { + try { + final IBinder token = new Binder( + "android.hardware.input.VirtualDpad:" + inputDeviceName); + mVirtualDevice.createVirtualDpad(display.getDisplay().getDisplayId(), + inputDeviceName, vendorId, productId, token); + return new VirtualDpad(mVirtualDevice, token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Creates a virtual keyboard. * * @param display the display that the events inputted through this device should target diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java index c59d7571ee6d..257ad7162e9e 100644 --- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java @@ -315,17 +315,17 @@ public interface BiometricFingerprintConstants { int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; /** - * Whether the FingerprintAcquired message is a signal to turn off HBM + * Whether the FingerprintAcquired message is a signal to disable the UDFPS display mode. + * We want to disable the UDFPS mode as soon as possible to conserve power and provide better + * UX. For example, prolonged high-brightness illumination of optical sensors can be unpleasant + * to the user, can cause long term display burn-in, and can drain the battery faster. */ - static boolean shouldTurnOffHbm(@FingerprintAcquired int acquiredInfo) { + static boolean shouldDisableUdfpsDisplayMode(@FingerprintAcquired int acquiredInfo) { switch (acquiredInfo) { case FINGERPRINT_ACQUIRED_START: - // Authentication just began + // Keep the UDFPS mode because the authentication just began. return false; case FINGERPRINT_ACQUIRED_GOOD: - // Good image captured. Turn off HBM. Success/Reject comes after, which is when - // hideUdfpsOverlay will be called. - return true; case FINGERPRINT_ACQUIRED_PARTIAL: case FINGERPRINT_ACQUIRED_INSUFFICIENT: case FINGERPRINT_ACQUIRED_IMAGER_DIRTY: @@ -334,11 +334,12 @@ public interface BiometricFingerprintConstants { case FINGERPRINT_ACQUIRED_IMMOBILE: case FINGERPRINT_ACQUIRED_TOO_BRIGHT: case FINGERPRINT_ACQUIRED_VENDOR: - // Bad image captured. Turn off HBM. Matcher will not run, so there's no need to - // keep HBM on. + // Disable the UDFPS mode because the image capture has finished. The overlay + // can be hidden later, once the authentication result arrives. return true; case FINGERPRINT_ACQUIRED_UNKNOWN: default: + // Keep the UDFPS mode in case of an unknown message. return false; } } diff --git a/core/java/android/hardware/input/VirtualDpad.java b/core/java/android/hardware/input/VirtualDpad.java new file mode 100644 index 000000000000..d7cda9ec33cf --- /dev/null +++ b/core/java/android/hardware/input/VirtualDpad.java @@ -0,0 +1,94 @@ +/* + * 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.hardware.input; + +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.companion.virtual.IVirtualDevice; +import android.os.IBinder; +import android.os.RemoteException; +import android.view.KeyEvent; + +import java.io.Closeable; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * A virtual dpad representing a key input mechanism on a remote device. + * + * This registers an InputDevice that is interpreted like a physically-connected device and + * dispatches received events to it. + * + * @hide + */ +@SystemApi +public class VirtualDpad implements Closeable { + + private final Set<Integer> mSupportedKeyCodes = + Collections.unmodifiableSet( + new HashSet<>( + Arrays.asList( + KeyEvent.KEYCODE_DPAD_UP, + KeyEvent.KEYCODE_DPAD_DOWN, + KeyEvent.KEYCODE_DPAD_LEFT, + KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.KEYCODE_DPAD_CENTER))); + private final IVirtualDevice mVirtualDevice; + private final IBinder mToken; + + /** @hide */ + public VirtualDpad(IVirtualDevice virtualDevice, IBinder token) { + mVirtualDevice = virtualDevice; + mToken = token; + } + + @Override + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void close() { + try { + mVirtualDevice.unregisterInputDevice(mToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sends a key event to the system. + * + * Supported key codes are KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, + * KEYCODE_DPAD_RIGHT and KEYCODE_DPAD_CENTER, + * + * @param event the event to send + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void sendKeyEvent(@NonNull VirtualKeyEvent event) { + try { + if (!mSupportedKeyCodes.contains(event.getKeyCode())) { + throw new IllegalArgumentException( + "Unsupported key code " + + event.getKeyCode() + + " sent to a VirtualDpad input device."); + } + mVirtualDevice.sendDpadKeyEvent(mToken, event); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/hardware/input/VirtualKeyEvent.java b/core/java/android/hardware/input/VirtualKeyEvent.java index 80a49c925208..dc30e55e36fc 100644 --- a/core/java/android/hardware/input/VirtualKeyEvent.java +++ b/core/java/android/hardware/input/VirtualKeyEvent.java @@ -158,6 +158,7 @@ public final class VirtualKeyEvent implements Parcelable { KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_MOVE_END, KeyEvent.KEYCODE_MOVE_HOME, KeyEvent.KEYCODE_PAGE_DOWN, diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java index ee9b659e9521..901401fea32c 100644 --- a/core/java/android/hardware/input/VirtualKeyboard.java +++ b/core/java/android/hardware/input/VirtualKeyboard.java @@ -22,6 +22,7 @@ import android.annotation.SystemApi; import android.companion.virtual.IVirtualDevice; import android.os.IBinder; import android.os.RemoteException; +import android.view.KeyEvent; import java.io.Closeable; @@ -37,6 +38,7 @@ import java.io.Closeable; @SystemApi public class VirtualKeyboard implements Closeable { + private final int mUnsupportedKeyCode = KeyEvent.KEYCODE_DPAD_CENTER; private final IVirtualDevice mVirtualDevice; private final IBinder mToken; @@ -64,6 +66,11 @@ public class VirtualKeyboard implements Closeable { @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendKeyEvent(@NonNull VirtualKeyEvent event) { try { + if (mUnsupportedKeyCode == event.getKeyCode()) { + throw new IllegalArgumentException( + "Unsupported key code " + event.getKeyCode() + + " sent to a VirtualKeyboard input device."); + } mVirtualDevice.sendKeyEvent(mToken, event); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 3a157d30d4c4..7436601f5352 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -1744,7 +1744,16 @@ public class InputMethodService extends AbstractInputMethodService { /** * Implement to return our standard {@link InputMethodImpl}. Subclasses * can override to provide their own customized version. + * + * @deprecated IME developers don't need to override this method to get callbacks information. + * Most methods in {@link InputMethodImpl} have corresponding callbacks. + * Use {@link InputMethodService#onBindInput()}, {@link InputMethodService#onUnbindInput()}, + * {@link InputMethodService#onWindowShown()}, {@link InputMethodService#onWindowHidden()}, etc. + * + * <p>Starting from Android U and later, override this method won't guarantee that IME works + * as previous platform behavior.</p> */ + @Deprecated @Override public AbstractInputMethodImpl onCreateInputMethodInterface() { return new InputMethodImpl(); @@ -1753,7 +1762,15 @@ public class InputMethodService extends AbstractInputMethodService { /** * Implement to return our standard {@link InputMethodSessionImpl}. Subclasses * can override to provide their own customized version. + * + * @deprecated IME developers don't need to override this method to get callbacks information. + * Most methods in {@link InputMethodSessionImpl} have corresponding callbacks. + * Use {@link InputMethodService#onFinishInput()}, + * {@link InputMethodService#onDisplayCompletions(CompletionInfo[])}, + * {@link InputMethodService#onUpdateExtractedText(int, ExtractedText)}, + * {@link InputMethodService#onUpdateSelection(int, int, int, int, int, int)} instead. */ + @Deprecated @Override public AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface() { return new InputMethodSessionImpl(); diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java index 3979c6c78dcb..10ce3bf78b2a 100644 --- a/core/java/android/net/Ikev2VpnProfile.java +++ b/core/java/android/net/Ikev2VpnProfile.java @@ -483,9 +483,6 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */, mIsRestrictedToTestNetworks, mExcludeLocalRoutes, mRequiresInternetValidation, mIkeTunConnParams); - - profile.server = getServerAddr(); - profile.ipsecIdentifier = getUserIdentity(); profile.proxy = mProxyInfo; profile.isBypassable = mIsBypassable; profile.isMetered = mIsMetered; @@ -499,6 +496,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { } profile.type = mType; + profile.server = getServerAddr(); + profile.ipsecIdentifier = getUserIdentity(); profile.setAllowedAlgorithms(mAllowedAlgorithms); switch (mType) { case TYPE_IKEV2_IPSEC_USER_PASS: diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 113a64048796..a6b62b0b7930 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -441,7 +441,7 @@ public final class StrictMode { * different penalties for different detected actions. */ public static final class ThreadPolicy { - /** The default, lax policy which doesn't catch anything. */ + /** The lax policy which doesn't catch anything. */ public static final ThreadPolicy LAX = new ThreadPolicy(0, null, null); @UnsupportedAppUsage @@ -728,7 +728,7 @@ public final class StrictMode { * <p>The policy is enabled by {@link #setVmPolicy}. */ public static final class VmPolicy { - /** The default, lax policy which doesn't catch anything. */ + /** The lax policy which doesn't catch anything. */ public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP, null, null); @UnsupportedAppUsage diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java index ecea054db216..2b75a23505d6 100644 --- a/core/java/android/os/SystemClock.java +++ b/core/java/android/os/SystemClock.java @@ -377,7 +377,7 @@ public final class SystemClock { } long currentNanos = elapsedRealtimeNanos(); long deltaMs = (currentNanos - time.getElapsedRealtimeNanos()) / 1000000L; - return time.getTime() + deltaMs; + return time.getUnixEpochTimeMillis() + deltaMs; } }; } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index f6aaee8e3de0..1e91700fcf01 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2843,7 +2843,6 @@ public class UserManager { /** * @hide */ - @TestApi public static boolean isUsersOnSecondaryDisplaysEnabled() { return SystemProperties.getBoolean("fw.users_on_secondary_displays", Resources.getSystem() @@ -2853,10 +2852,12 @@ public class UserManager { /** * Returns whether the device allows users to run (and launch activities) on secondary displays. * - * @return {@code false} for most devices, except automotive vehicles with passenger displays. + * @return {@code false} for most devices, except on automotive builds for vehiches with + * passenger displays. * * @hide */ + @TestApi public boolean isUsersOnSecondaryDisplaysSupported() { return isUsersOnSecondaryDisplaysEnabled(); } diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index fe46c9ee00f2..345486e390cd 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -203,6 +203,15 @@ public final class DeviceConfig { @SystemApi public static final String NAMESPACE_TETHERING = "tethering"; + + /** + * Namespace for Nearby module. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_NEARBY = "nearby"; + /** * Namespace for content capture feature used by on-device machine intelligence * to provide suggestions in a privacy-safe manner. @@ -776,6 +785,14 @@ public final class DeviceConfig { public static final String NAMESPACE_WEAR = "wear"; /** + * Namespace for features relating to MBA transparency metadata. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_TRANSPARENCY_METADATA = "transparency_metadata"; + + /** * Namespace for the input method manager platform features. * * @hide diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a99d0f0a5656..3a9af7ef4ed2 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9825,6 +9825,7 @@ public final class Settings { * Whether or not virtual sensors are enabled. * @hide */ + @TestApi @Readable public static final String BIOMETRIC_VIRTUAL_ENABLED = "biometric_virtual_enabled"; diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index f90055829b89..8efc5eb6b6ff 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -68,6 +68,8 @@ public final class KeymasterDefs { public static final int KM_TAG_RSA_PUBLIC_EXPONENT = Tag.RSA_PUBLIC_EXPONENT; // KM_ULONG | 200; public static final int KM_TAG_INCLUDE_UNIQUE_ID = Tag.INCLUDE_UNIQUE_ID; // KM_BOOL | 202; + public static final int KM_TAG_RSA_OAEP_MGF_DIGEST = Tag.RSA_OAEP_MGF_DIGEST; + // KM_ENUM_REP | 203; public static final int KM_TAG_ACTIVE_DATETIME = Tag.ACTIVE_DATETIME; // KM_DATE | 400; public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java index 537dffc4abf1..d48d566fd860 100755 --- a/core/java/android/text/format/DateFormat.java +++ b/core/java/android/text/format/DateFormat.java @@ -170,7 +170,7 @@ public class DateFormat { * mean using 12-hour in some locales and, in this case, is duplicated as the 'a' field. */ @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT) + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) static final long DISALLOW_DUPLICATE_FIELD_IN_SKELETON = 170233598L; /** diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 20f01ae3ae0d..cb8f0af10c47 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -96,13 +96,6 @@ public class FeatureFlagUtils { /** @hide */ public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping"; - - /** Support Clear Calling feature. - * @hide - */ - public static final String SETTINGS_ENABLE_CLEAR_CALLING = "settings_enable_clear_calling"; - - /** Flag to enable / disable the Simple Cursor accessibility feature in * Settings. * @hide @@ -122,6 +115,11 @@ public class FeatureFlagUtils { */ public static final String SETTINGS_ENABLE_SPA = "settings_enable_spa"; + /** Flag to enable/disable adb log metrics + * @hide + */ + public static final String SETTINGS_ADB_METRICS_WRITER = "settings_adb_metrics_writer"; + private static final Map<String, String> DEFAULT_FLAGS; static { @@ -151,10 +149,10 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true"); 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_ENABLE_CLEAR_CALLING, "false"); DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_SIMPLE_CURSOR, "false"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false"); + DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false"); } private static final Set<String> PERSISTENT_FLAGS; diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index b2a26fa665f8..6b9fceb73ec1 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -67,6 +67,7 @@ import android.view.SurfaceControl; import android.view.displayhash.DisplayHash; import android.view.displayhash.VerifiedDisplayHash; import android.window.ITaskFpsCallback; +import android.window.ScreenCapture; /** * System private interface to the window manager. @@ -968,4 +969,7 @@ interface IWindowManager * treatment. */ boolean isLetterboxBackgroundMultiColored(); + + oneway void captureDisplay(int displayId, in @nullable ScreenCapture.CaptureArgs captureArgs, + in ScreenCapture.ScreenCaptureListener listener); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 15aa4b44ac26..894ce63ca64e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -24515,9 +24515,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Set the current default focus highlight. * @param highlight the highlight drawable, or {@code null} if it's no longer needed. - * @hide */ - void setDefaultFocusHighlight(Drawable highlight) { + private void setDefaultFocusHighlight(Drawable highlight) { mDefaultFocusHighlight = highlight; mDefaultFocusHighlightSizeChanged = true; if (highlight != null) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4b167880582c..b6f775d3e536 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3098,7 +3098,8 @@ public final class ViewRootImpl implements ViewParent, // WindowManagerService has reported back a frame from a configuration not yet // handled by the client. In this case, we need to accept the configuration so we // do not lay out and draw with the wrong configuration. - if (!mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) { + if (mRelayoutRequested + && !mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) { if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: " + mPendingMergedConfiguration.getMergedConfiguration()); performConfigurationChange(new MergedConfiguration(mPendingMergedConfiguration), @@ -3814,13 +3815,6 @@ public final class ViewRootImpl implements ViewParent, if (mAttachInfo.mTooltipHost != null) { mAttachInfo.mTooltipHost.hideTooltip(); } - if (!hasWindowFocus) { - // Clear focus highlight if its window lost focus. - final View focused = mView.findFocus(); - if (focused != null) { - focused.setDefaultFocusHighlight(null); - } - } } // Note: must be done after the focus change callbacks, @@ -5852,13 +5846,7 @@ public final class ViewRootImpl implements ViewParent, // be when the window is first being added, and mFocused isn't // set yet. final View focused = mView.findFocus(); - if (focused == null) { - return false; - } - - // Clear default focus highlight if it entered touch mode. - focused.setDefaultFocusHighlight(null); - if (!focused.isFocusableInTouchMode()) { + if (focused != null && !focused.isFocusableInTouchMode()) { final ViewGroup ancestorToTakeFocus = findAncestorToTakeFocusInTouchMode(focused); if (ancestorToTakeFocus != null) { // there is an ancestor that wants focus after its @@ -8158,7 +8146,8 @@ public final class ViewRootImpl implements ViewParent, final int measuredWidth = mView.getMeasuredWidth(); final int measuredHeight = mView.getMeasuredHeight(); final boolean relayoutAsync; - if (LOCAL_LAYOUT && !mFirst && viewVisibility == mViewVisibility + if (LOCAL_LAYOUT + && (mViewFrameInfo.flags & FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED) == 0 && mWindowAttributes.type != TYPE_APPLICATION_STARTING && mSyncSeqId <= mLastSyncSeqId && winConfigFromAm.diff(winConfigFromWm, false /* compareUndefined */) == 0) { diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 0c7c1639e6c7..c966e93a71c5 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -788,7 +788,7 @@ public final class InputMethodManager { // we'll just do a window focus gain and call it a day. View servedView = controller.getServedView(); boolean nextFocusHasConnection = servedView != null && servedView == focusedView - && hasActiveConnection(focusedView); + && hasActiveInputConnectionInternal(focusedView); if (DEBUG) { Log.v(TAG, "Reporting focus gain, without startInput" + ", nextFocusIsServedView=" + nextFocusHasConnection); @@ -861,22 +861,11 @@ public final class InputMethodManager { /** * Checks whether the active input connection (if any) is for the given view. * - * TODO(b/182259171): Clean-up hasActiveConnection to simplify the logic. - * - * Note that this method is only intended for restarting input after focus gain - * (e.g. b/160391516), DO NOT leverage this method to do another check. + * @see #hasActiveInputConnectionInternal(View)} */ @Override public boolean hasActiveConnection(View view) { - synchronized (mH) { - if (!hasServedByInputMethodLocked(view) || !isImeSessionAvailableLocked()) { - return false; - } - - return mServedInputConnection != null - && mServedInputConnection.isActive() - && mServedInputConnection.getServedView() == view; - } + return hasActiveInputConnectionInternal(view); } } @@ -889,11 +878,31 @@ public final class InputMethodManager { * Checks whether the active input connection (if any) is for the given view. * * @hide - * @see ImeFocusController#getImmDelegate()#hasActiveInputConnection(View) + * @see #hasActiveInputConnectionInternal(View)} */ @TestApi public boolean hasActiveInputConnection(@Nullable View view) { - return mDelegate.hasActiveConnection(view); + return hasActiveInputConnectionInternal(view); + } + + /** + * Checks whether the active input connection (if any) is for the given view. + * + * TODO(b/182259171): Clean-up hasActiveConnection to simplify the logic. + * + * Note that this method is only intended for restarting input after focus gain + * (e.g. b/160391516), DO NOT leverage this method to do another check. + */ + private boolean hasActiveInputConnectionInternal(@Nullable View view) { + synchronized (mH) { + if (!hasServedByInputMethodLocked(view) || !isImeSessionAvailableLocked()) { + return false; + } + + return mServedInputConnection != null + && mServedInputConnection.isActive() + && mServedInputConnection.getServedView() == view; + } } @GuardedBy("mH") diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index a2a198ac8aea..424b8aef7dae 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -2875,6 +2875,17 @@ public class Editor { } } + /** + * + * @return whether the Blink runnable is blinking or not, if null return false. + * @hide + */ + @VisibleForTesting + public boolean isBlinking() { + if (mBlink == null) return false; + return !mBlink.mCancelled; + } + private class Blink implements Runnable { private boolean mCancelled; diff --git a/core/java/android/window/ScreenCapture.aidl b/core/java/android/window/ScreenCapture.aidl new file mode 100644 index 000000000000..267a7c60b60d --- /dev/null +++ b/core/java/android/window/ScreenCapture.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +/** @hide */ +parcelable ScreenCapture.CaptureArgs; + +/** @hide */ +parcelable ScreenCapture.ScreenshotHardwareBuffer; + +/** @hide */ +parcelable ScreenCapture.ScreenCaptureListener;
\ No newline at end of file diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java index 887d027d26a8..c010f2248dc5 100644 --- a/core/java/android/window/ScreenCapture.java +++ b/core/java/android/window/ScreenCapture.java @@ -24,11 +24,16 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; import android.util.Log; import android.view.SurfaceControl; +import libcore.util.NativeAllocationRegistry; + import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; /** * Handles display and layer captures for the system. @@ -39,18 +44,23 @@ public class ScreenCapture { private static final String TAG = "ScreenCapture"; private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs, - ScreenCaptureListener captureListener); + long captureListener); private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs, - ScreenCaptureListener captureListener); + long captureListener); + private static native long nativeCreateScreenCaptureListener( + Consumer<ScreenshotHardwareBuffer> consumer); + private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out); + private static native long nativeReadListenerFromParcel(Parcel in); + private static native long getNativeListenerFinalizer(); /** - * @param captureArgs Arguments about how to take the screenshot + * @param captureArgs Arguments about how to take the screenshot * @param captureListener A listener to receive the screenshot callback * @hide */ public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs, @NonNull ScreenCaptureListener captureListener) { - return nativeCaptureDisplay(captureArgs, captureListener); + return nativeCaptureDisplay(captureArgs, captureListener.mNativeObject); } /** @@ -61,10 +71,8 @@ public class ScreenCapture { */ public static ScreenshotHardwareBuffer captureDisplay( DisplayCaptureArgs captureArgs) { - SyncScreenCaptureListener - screenCaptureListener = new SyncScreenCaptureListener(); - - int status = captureDisplay(captureArgs, screenCaptureListener); + SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener(); + int status = captureDisplay(captureArgs, screenCaptureListener.getScreenCaptureListener()); if (status != 0) { return null; } @@ -75,14 +83,13 @@ public class ScreenCapture { /** * Captures a layer and its children and returns a {@link HardwareBuffer} with the content. * - * @param layer The root layer to capture. - * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new - * Rect()' or null if no cropping is desired. If the root layer does not - * have a buffer or a crop set, then a non-empty source crop must be - * specified. - * @param frameScale The desired scale of the returned buffer; the raw - * screen will be scaled up/down. - * + * @param layer The root layer to capture. + * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new + * Rect()' or null if no cropping is desired. If the root layer does not + * have a buffer or a crop set, then a non-empty source crop must be + * specified. + * @param frameScale The desired scale of the returned buffer; the raw screen will be scaled + * up/down. * @return Returns a HardwareBuffer that contains the layer capture. * @hide */ @@ -94,15 +101,14 @@ public class ScreenCapture { /** * Captures a layer and its children and returns a {@link HardwareBuffer} with the content. * - * @param layer The root layer to capture. - * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new - * Rect()' or null if no cropping is desired. If the root layer does not - * have a buffer or a crop set, then a non-empty source crop must be - * specified. - * @param frameScale The desired scale of the returned buffer; the raw - * screen will be scaled up/down. - * @param format The desired pixel format of the returned buffer. - * + * @param layer The root layer to capture. + * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new + * Rect()' or null if no cropping is desired. If the root layer does not + * have a buffer or a crop set, then a non-empty source crop must be + * specified. + * @param frameScale The desired scale of the returned buffer; the raw screen will be scaled + * up/down. + * @param format The desired pixel format of the returned buffer. * @return Returns a HardwareBuffer that contains the layer capture. * @hide */ @@ -124,7 +130,7 @@ public class ScreenCapture { LayerCaptureArgs captureArgs) { SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener(); - int status = captureLayers(captureArgs, screenCaptureListener); + int status = captureLayers(captureArgs, screenCaptureListener.getScreenCaptureListener()); if (status != 0) { return null; } @@ -135,6 +141,7 @@ public class ScreenCapture { /** * Like {@link #captureLayers(SurfaceControl, Rect, float, int)} but with an array of layer * handles to exclude. + * * @hide */ public static ScreenshotHardwareBuffer captureLayersExcluding(SurfaceControl layer, @@ -150,24 +157,13 @@ public class ScreenCapture { } /** - * @param captureArgs Arguments about how to take the screenshot + * @param captureArgs Arguments about how to take the screenshot * @param captureListener A listener to receive the screenshot callback * @hide */ public static int captureLayers(@NonNull LayerCaptureArgs captureArgs, @NonNull ScreenCaptureListener captureListener) { - return nativeCaptureLayers(captureArgs, captureListener); - } - - /** - * @hide - */ - public interface ScreenCaptureListener { - /** - * The callback invoked when the screen capture is complete. - * @param hardwareBuffer Data containing info about the screen capture. - */ - void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer); + return nativeCaptureLayers(captureArgs, captureListener.mNativeObject); } /** @@ -190,15 +186,16 @@ public class ScreenCapture { mContainsHdrLayers = containsHdrLayers; } - /** - * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object. - * @param hardwareBuffer The existing HardwareBuffer object - * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named} - * @param containsSecureLayers Indicates whether this graphic buffer contains captured - * contents of secure layers, in which case the screenshot - * should not be persisted. - * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content. - */ + /** + * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object. + * + * @param hardwareBuffer The existing HardwareBuffer object + * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named} + * @param containsSecureLayers Indicates whether this graphic buffer contains captured + * contents of secure layers, in which case the screenshot + * should not be persisted. + * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content. + */ private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer, int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) { ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]); @@ -220,6 +217,7 @@ public class ScreenCapture { public boolean containsSecureLayers() { return mContainsSecureLayers; } + /** * Returns whether the screenshot contains at least one HDR layer. * This information may be useful for informing the display whether this screenshot @@ -234,7 +232,7 @@ public class ScreenCapture { * Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap * into * a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)} - * + * <p> * CAVEAT: This can be extremely slow; avoid use unless absolutely necessary; prefer to * directly * use the {@link HardwareBuffer} directly. @@ -250,34 +248,13 @@ public class ScreenCapture { } } - private static class SyncScreenCaptureListener implements ScreenCaptureListener { - private static final int SCREENSHOT_WAIT_TIME_S = 1; - private ScreenshotHardwareBuffer mScreenshotHardwareBuffer; - private final CountDownLatch mCountDownLatch = new CountDownLatch(1); - - @Override - public void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer) { - mScreenshotHardwareBuffer = hardwareBuffer; - mCountDownLatch.countDown(); - } - - private ScreenshotHardwareBuffer waitForScreenshot() { - try { - mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS); - } catch (Exception e) { - Log.e(TAG, "Failed to wait for screen capture result", e); - } - - return mScreenshotHardwareBuffer; - } - } - /** * A common arguments class used for various screenshot requests. This contains arguments that * are shared between {@link DisplayCaptureArgs} and {@link LayerCaptureArgs} + * * @hide */ - private abstract static class CaptureArgs { + public static class CaptureArgs implements Parcelable { private final int mPixelFormat; private final Rect mSourceCrop = new Rect(); private final float mFrameScaleX; @@ -287,7 +264,7 @@ public class ScreenCapture { private final long mUid; private final boolean mGrayscale; - private CaptureArgs(Builder<? extends Builder<?>> builder) { + private CaptureArgs(CaptureArgs.Builder<? extends CaptureArgs.Builder<?>> builder) { mPixelFormat = builder.mPixelFormat; mSourceCrop.set(builder.mSourceCrop); mFrameScaleX = builder.mFrameScaleX; @@ -298,12 +275,23 @@ public class ScreenCapture { mGrayscale = builder.mGrayscale; } + private CaptureArgs(Parcel in) { + mPixelFormat = in.readInt(); + mSourceCrop.readFromParcel(in); + mFrameScaleX = in.readFloat(); + mFrameScaleY = in.readFloat(); + mCaptureSecureLayers = in.readBoolean(); + mAllowProtected = in.readBoolean(); + mUid = in.readLong(); + mGrayscale = in.readBoolean(); + } + /** * The Builder class used to construct {@link CaptureArgs} * - * @param <T> A builder that extends {@link Builder} + * @param <T> A builder that extends {@link CaptureArgs.Builder} */ - abstract static class Builder<T extends Builder<T>> { + public static class Builder<T extends CaptureArgs.Builder<T>> { private int mPixelFormat = PixelFormat.RGBA_8888; private final Rect mSourceCrop = new Rect(); private float mFrameScaleX = 1; @@ -314,6 +302,14 @@ public class ScreenCapture { private boolean mGrayscale; /** + * Construct a new {@link CaptureArgs} with the set parameters. The builder remains + * valid. + */ + public CaptureArgs build() { + return new CaptureArgs(this); + } + + /** * The desired pixel format of the returned buffer. */ public T setPixelFormat(int pixelFormat) { @@ -395,15 +391,47 @@ public class ScreenCapture { /** * Each sub class should return itself to allow the builder to chain properly */ - abstract T getThis(); + T getThis() { + return (T) this; + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mPixelFormat); + mSourceCrop.writeToParcel(dest, flags); + dest.writeFloat(mFrameScaleX); + dest.writeFloat(mFrameScaleY); + dest.writeBoolean(mCaptureSecureLayers); + dest.writeBoolean(mAllowProtected); + dest.writeLong(mUid); + dest.writeBoolean(mGrayscale); } + + public static final Parcelable.Creator<CaptureArgs> CREATOR = + new Parcelable.Creator<CaptureArgs>() { + @Override + public CaptureArgs createFromParcel(Parcel in) { + return new CaptureArgs(in); + } + + @Override + public CaptureArgs[] newArray(int size) { + return new CaptureArgs[size]; + } + }; } /** * The arguments class used to make display capture requests. * - * @see #nativeCaptureDisplay(DisplayCaptureArgs, ScreenCaptureListener) * @hide + * @see #nativeCaptureDisplay(DisplayCaptureArgs, long) */ public static class DisplayCaptureArgs extends CaptureArgs { private final IBinder mDisplayToken; @@ -488,8 +516,8 @@ public class ScreenCapture { /** * The arguments class used to make layer capture requests. * - * @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener) * @hide + * @see #nativeCaptureLayers(LayerCaptureArgs, long) */ public static class LayerCaptureArgs extends CaptureArgs { private final long mNativeLayer; @@ -530,6 +558,17 @@ public class ScreenCapture { return new LayerCaptureArgs(this); } + public Builder(SurfaceControl layer, CaptureArgs args) { + setLayer(layer); + setPixelFormat(args.mPixelFormat); + setSourceCrop(args.mSourceCrop); + setFrameScale(args.mFrameScaleX, args.mFrameScaleY); + setCaptureSecureLayers(args.mCaptureSecureLayers); + setAllowProtected(args.mAllowProtected); + setUid(args.mUid); + setGrayscale(args.mGrayscale); + } + public Builder(SurfaceControl layer) { setLayer(layer); } @@ -542,7 +581,6 @@ public class ScreenCapture { return this; } - /** * An array of layer handles to exclude. */ @@ -564,8 +602,106 @@ public class ScreenCapture { Builder getThis() { return this; } + } + } + /** + * The object used to receive the results when invoking screen capture requests via + * {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)} or + * {@link #captureLayers(LayerCaptureArgs, ScreenCaptureListener)} + */ + public static class ScreenCaptureListener implements Parcelable { + private final long mNativeObject; + private static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + ScreenCaptureListener.class.getClassLoader(), getNativeListenerFinalizer()); + + /** + * @param consumer The callback invoked when the screen capture is complete. + */ + public ScreenCaptureListener(Consumer<ScreenshotHardwareBuffer> consumer) { + mNativeObject = nativeCreateScreenCaptureListener(consumer); + sRegistry.registerNativeAllocation(this, mNativeObject); } + + private ScreenCaptureListener(Parcel in) { + if (in.readBoolean()) { + mNativeObject = nativeReadListenerFromParcel(in); + sRegistry.registerNativeAllocation(this, mNativeObject); + } else { + mNativeObject = 0; + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + if (mNativeObject == 0) { + dest.writeBoolean(false); + } else { + dest.writeBoolean(true); + nativeWriteListenerToParcel(mNativeObject, dest); + } + } + + public static final Parcelable.Creator<ScreenCaptureListener> CREATOR = + new Parcelable.Creator<ScreenCaptureListener>() { + @Override + public ScreenCaptureListener createFromParcel(Parcel in) { + return new ScreenCaptureListener(in); + } + + @Override + public ScreenCaptureListener[] newArray(int size) { + return new ScreenCaptureListener[0]; + } + }; } + /** + * A helper class to handle the async screencapture callbacks synchronously. This should only + * be used if the screencapture caller doesn't care that it blocks waiting for a screenshot. + */ + public static class SyncScreenCaptureListener { + private static final int SCREENSHOT_WAIT_TIME_S = 1; + private ScreenshotHardwareBuffer mScreenshotHardwareBuffer; + private final CountDownLatch mCountDownLatch = new CountDownLatch(1); + private final ScreenCaptureListener mScreenCaptureListener; + + public SyncScreenCaptureListener() { + mScreenCaptureListener = new ScreenCaptureListener(screenshotHardwareBuffer -> { + mScreenshotHardwareBuffer = screenshotHardwareBuffer; + mCountDownLatch.countDown(); + }); + } + + /** + * @return The underlying {@link ScreenCaptureListener} + */ + public ScreenCaptureListener getScreenCaptureListener() { + return mScreenCaptureListener; + } + + /** + * Waits until the screenshot callback has been invoked and the screenshot is ready. This + * can return {@code null} if the screenshot callback wasn't invoked after + * {@link #SCREENSHOT_WAIT_TIME_S} or the screencapture request resulted in an error + * + * @return A ScreenshotHardwareBuffer for the content that was captured. + */ + @Nullable + public ScreenshotHardwareBuffer waitForScreenshot() { + try { + mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS); + } catch (Exception e) { + Log.e(TAG, "Failed to wait for screen capture result", e); + } + + return mScreenshotHardwareBuffer; + } + } } diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 83bf801391f0..8d51c9cdca9c 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -270,7 +270,6 @@ interface IVoiceInteractionManagerService { */ void shutdownHotwordDetectionService(); - @EnforcePermission(allOf={"RECORD_AUDIO", "CAPTURE_AUDIO_HOTWORD"}) void startListeningFromMic( in AudioFormat audioFormat, in IMicrophoneHotwordDetectionVoiceInteractionCallback callback); @@ -286,7 +285,6 @@ interface IVoiceInteractionManagerService { /** * Test API to simulate to trigger hardware recognition event for test. */ - @EnforcePermission(allOf={"RECORD_AUDIO", "CAPTURE_AUDIO_HOTWORD"}) void triggerHardwareRecognitionEventForTest( in SoundTrigger.KeyphraseRecognitionEvent event, in IHotwordRecognitionStatusCallback callback); diff --git a/core/java/com/android/internal/jank/TEST_MAPPING b/core/java/com/android/internal/jank/TEST_MAPPING new file mode 100644 index 000000000000..4e00ff19e9d9 --- /dev/null +++ b/core/java/com/android/internal/jank/TEST_MAPPING @@ -0,0 +1,22 @@ +{ + "presubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "com.android.internal.jank" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ], + "file_patterns": [ + "core/java/com/android/internal/jank/.*", + "core/tests/coretests/src/com/android/internal/jank/.*" + ] + } + ] +} diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index 14a6d5e4d08c..d3f9e0a2729e 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -16,6 +16,27 @@ package com.android.internal.util; import static android.os.Trace.TRACE_TAG_APP; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL_UNLOCKED; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_EXPAND_PANEL; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FACE_WAKE_AND_UNLOCK; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FINGERPRINT_WAKE_AND_UNLOCK; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TURN_ON_SCREEN; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH; +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__UNKNOWN_ACTION; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,6 +51,7 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.EventLogTags; import com.android.internal.os.BackgroundThread; @@ -103,14 +125,14 @@ public class LatencyTracker { public static final int ACTION_START_RECENTS_ANIMATION = 8; /** - * Time it takes the sensor to detect rotation. + * Time it takes to for the camera based algorithm to rotate the screen. */ - public static final int ACTION_ROTATE_SCREEN_SENSOR = 9; + public static final int ACTION_ROTATE_SCREEN_CAMERA_CHECK = 9; /** - * Time it takes to for the camera based algorithm to rotate the screen. + * Time it takes the sensor to detect rotation. */ - public static final int ACTION_ROTATE_SCREEN_CAMERA_CHECK = 10; + public static final int ACTION_ROTATE_SCREEN_SENSOR = 10; /** * Time it takes to start unlock animation . @@ -143,9 +165,14 @@ public class LatencyTracker { public static final int ACTION_LOAD_SHARE_SHEET = 16; /** + * Time it takes for showing the selection toolbar. + */ + public static final int ACTION_SHOW_SELECTION_TOOLBAR = 17; + + /** * Time it takes to show AOD display after folding the device. */ - public static final int ACTION_FOLD_TO_AOD = 17; + public static final int ACTION_FOLD_TO_AOD = 18; private static final int[] ACTIONS_ALL = { ACTION_EXPAND_PANEL, @@ -157,14 +184,15 @@ public class LatencyTracker { ACTION_ROTATE_SCREEN, ACTION_FACE_WAKE_AND_UNLOCK, ACTION_START_RECENTS_ANIMATION, - ACTION_ROTATE_SCREEN_SENSOR, ACTION_ROTATE_SCREEN_CAMERA_CHECK, + ACTION_ROTATE_SCREEN_SENSOR, ACTION_LOCKSCREEN_UNLOCK, ACTION_USER_SWITCH, ACTION_SWITCH_DISPLAY_UNFOLD, ACTION_UDFPS_ILLUMINATE, ACTION_SHOW_BACK_ARROW, ACTION_LOAD_SHARE_SHEET, + ACTION_SHOW_SELECTION_TOOLBAR, ACTION_FOLD_TO_AOD, }; @@ -179,39 +207,42 @@ public class LatencyTracker { ACTION_ROTATE_SCREEN, ACTION_FACE_WAKE_AND_UNLOCK, ACTION_START_RECENTS_ANIMATION, - ACTION_ROTATE_SCREEN_SENSOR, ACTION_ROTATE_SCREEN_CAMERA_CHECK, + ACTION_ROTATE_SCREEN_SENSOR, ACTION_LOCKSCREEN_UNLOCK, ACTION_USER_SWITCH, ACTION_SWITCH_DISPLAY_UNFOLD, ACTION_UDFPS_ILLUMINATE, ACTION_SHOW_BACK_ARROW, ACTION_LOAD_SHARE_SHEET, - ACTION_FOLD_TO_AOD + ACTION_SHOW_SELECTION_TOOLBAR, + ACTION_FOLD_TO_AOD, }) @Retention(RetentionPolicy.SOURCE) public @interface Action { } - private static final int[] STATSD_ACTION = new int[]{ - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_EXPAND_PANEL, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FINGERPRINT_WAKE_AND_UNLOCK, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL_UNLOCKED, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TURN_ON_SCREEN, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FACE_WAKE_AND_UNLOCK, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD + @VisibleForTesting + public static final int[] STATSD_ACTION = new int[] { + UIACTION_LATENCY_REPORTED__ACTION__ACTION_EXPAND_PANEL, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_FINGERPRINT_WAKE_AND_UNLOCK, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL_UNLOCKED, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_TURN_ON_SCREEN, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_FACE_WAKE_AND_UNLOCK, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR, + UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD, }; private static LatencyTracker sLatencyTracker; @@ -269,43 +300,45 @@ public class LatencyTracker { public static String getNameOfAction(int atomsProtoAction) { // Defined in AtomsProto.java switch (atomsProtoAction) { - case 0: + case UIACTION_LATENCY_REPORTED__ACTION__UNKNOWN_ACTION: return "UNKNOWN"; - case 1: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_EXPAND_PANEL: return "ACTION_EXPAND_PANEL"; - case 2: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS: return "ACTION_TOGGLE_RECENTS"; - case 3: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_FINGERPRINT_WAKE_AND_UNLOCK: return "ACTION_FINGERPRINT_WAKE_AND_UNLOCK"; - case 4: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL: return "ACTION_CHECK_CREDENTIAL"; - case 5: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL_UNLOCKED: return "ACTION_CHECK_CREDENTIAL_UNLOCKED"; - case 6: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_TURN_ON_SCREEN: return "ACTION_TURN_ON_SCREEN"; - case 7: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN: return "ACTION_ROTATE_SCREEN"; - case 8: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_FACE_WAKE_AND_UNLOCK: return "ACTION_FACE_WAKE_AND_UNLOCK"; - case 9: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION: return "ACTION_START_RECENTS_ANIMATION"; - case 10: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK: return "ACTION_ROTATE_SCREEN_CAMERA_CHECK"; - case 11: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR: return "ACTION_ROTATE_SCREEN_SENSOR"; - case 12: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK: return "ACTION_LOCKSCREEN_UNLOCK"; - case 13: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH: return "ACTION_USER_SWITCH"; - case 14: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD: return "ACTION_SWITCH_DISPLAY_UNFOLD"; - case 15: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE: return "ACTION_UDFPS_ILLUMINATE"; - case 16: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW: return "ACTION_SHOW_BACK_ARROW"; - case 17: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET: return "ACTION_LOAD_SHARE_SHEET"; - case 19: + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR: + return "ACTION_SHOW_SELECTION_TOOLBAR"; + case UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD: return "ACTION_FOLD_TO_AOD"; default: throw new IllegalArgumentException("Invalid action"); diff --git a/core/java/com/android/internal/util/TEST_MAPPING b/core/java/com/android/internal/util/TEST_MAPPING index 41d59bbeb801..00a8118c0e4b 100644 --- a/core/java/com/android/internal/util/TEST_MAPPING +++ b/core/java/com/android/internal/util/TEST_MAPPING @@ -15,6 +15,21 @@ } ], "file_patterns": ["Xml"] + }, + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "com.android.internal.util.LatencyTrackerTest" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ], + "file_patterns": ["LatencyTracker.java"] } ] } diff --git a/core/java/com/android/internal/util/TypedProperties.java b/core/java/com/android/internal/util/TypedProperties.java index 5613999ea773..c10dbe7fdf3b 100644 --- a/core/java/com/android/internal/util/TypedProperties.java +++ b/core/java/com/android/internal/util/TypedProperties.java @@ -264,29 +264,29 @@ public class TypedProperties extends HashMap<String, Object> { // Ensure that the type can hold this value, and return. int width = (type >> 8) & 0xff; switch (width) { - case 1: - if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) { - throw new ParseException(st, "8-bit integer constant"); - } - return new Byte((byte)value); - case 2: - if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) { - throw new ParseException(st, "16-bit integer constant"); - } - return new Short((short)value); - case 4: - if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) { - throw new ParseException(st, "32-bit integer constant"); - } - return new Integer((int)value); - case 8: - if (value < Long.MIN_VALUE || value > Long.MAX_VALUE) { - throw new ParseException(st, "64-bit integer constant"); - } - return new Long(value); - default: - throw new IllegalStateException( - "Internal error; unexpected integer type width " + width); + case 1: + if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) { + throw new ParseException(st, "8-bit integer constant"); + } + return Byte.valueOf((byte) value); + case 2: + if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) { + throw new ParseException(st, "16-bit integer constant"); + } + return Short.valueOf((short) value); + case 4: + if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) { + throw new ParseException(st, "32-bit integer constant"); + } + return Integer.valueOf((int) value); + case 8: + if (value < Long.MIN_VALUE || value > Long.MAX_VALUE) { + throw new ParseException(st, "64-bit integer constant"); + } + return Long.valueOf(value); + default: + throw new IllegalStateException( + "Internal error; unexpected integer type width " + width); } } else if ((type & 0xff) == 'F') { if (token != StreamTokenizer.TT_WORD) { @@ -317,10 +317,10 @@ public class TypedProperties extends HashMap<String, Object> { throw new ParseException(st, "32-bit float constant"); } } - return new Float((float)value); + return Float.valueOf((float) value); } else { // This property is a double; no need to truncate. - return new Double(value); + return Double.valueOf(value); } } else if (type == TYPE_STRING) { // Expect a quoted string or the word "null". diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 14699e7097f4..a068008f5e22 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -22,7 +22,7 @@ per-file android_view_PointerIcon.* = file:/services/core/java/com/android/serve # WindowManager per-file android_graphics_BLASTBufferQueue.cpp = file:/services/core/java/com/android/server/wm/OWNERS per-file android_view_Surface* = file:/services/core/java/com/android/server/wm/OWNERS -per-file android_window_WindowInfosListener.cpp = file:/services/core/java/com/android/server/wm/OWNERS +per-file android_window_* = file:/services/core/java/com/android/server/wm/OWNERS # Resources per-file android_content_res_* = file:/core/java/android/content/res/OWNERS diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 746f88cd823d..77317d1c3c1a 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2937,13 +2937,14 @@ static jint android_media_AudioSystem_getDirectProfilesForAttributes(JNIEnv *env for (const auto &audioProfile : audioProfiles) { jobject jAudioProfile; - jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &audioProfile, false); - if (jStatus == AUDIO_JAVA_BAD_VALUE) { + jint jConvertProfileStatus = convertAudioProfileFromNative( + env, &jAudioProfile, &audioProfile, false); + if (jConvertProfileStatus == AUDIO_JAVA_BAD_VALUE) { // skipping Java layer unsupported audio formats continue; } - if (jStatus != AUDIO_JAVA_SUCCESS) { - return jStatus; + if (jConvertProfileStatus != AUDIO_JAVA_SUCCESS) { + return jConvertProfileStatus; } env->CallBooleanMethod(jAudioProfilesList, gArrayListMethods.add, jAudioProfile); env->DeleteLocalRef(jAudioProfile); diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp index 3bada15aa834..c1929c6535fb 100644 --- a/core/jni/android_window_ScreenCapture.cpp +++ b/core/jni/android_window_ScreenCapture.cpp @@ -61,9 +61,8 @@ static struct { } gLayerCaptureArgsClassInfo; static struct { - jclass clazz; - jmethodID onScreenCaptureComplete; -} gScreenCaptureListenerClassInfo; + jmethodID accept; +} gConsumerClassInfo; static struct { jclass clazz; @@ -98,14 +97,14 @@ class ScreenCaptureListenerWrapper : public gui::BnScreenCaptureListener { public: explicit ScreenCaptureListenerWrapper(JNIEnv* env, jobject jobject) { env->GetJavaVM(&mVm); - mScreenCaptureListenerObject = env->NewGlobalRef(jobject); - LOG_ALWAYS_FATAL_IF(!mScreenCaptureListenerObject, "Failed to make global ref"); + mConsumerObject = env->NewGlobalRef(jobject); + LOG_ALWAYS_FATAL_IF(!mConsumerObject, "Failed to make global ref"); } ~ScreenCaptureListenerWrapper() { - if (mScreenCaptureListenerObject) { - getenv()->DeleteGlobalRef(mScreenCaptureListenerObject); - mScreenCaptureListenerObject = nullptr; + if (mConsumerObject) { + getenv()->DeleteGlobalRef(mConsumerObject); + mConsumerObject = nullptr; } } @@ -113,9 +112,8 @@ public: const gui::ScreenCaptureResults& captureResults) override { JNIEnv* env = getenv(); if (!captureResults.fenceResult.ok() || captureResults.buffer == nullptr) { - env->CallVoidMethod(mScreenCaptureListenerObject, - gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr); - checkAndClearException(env, "onScreenCaptureComplete"); + env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, nullptr); + checkAndClearException(env, "accept"); return binder::Status::ok(); } captureResults.fenceResult.value()->waitForever(LOG_TAG); @@ -130,17 +128,15 @@ public: captureResults.capturedSecureLayers, captureResults.capturedHdrLayers); checkAndClearException(env, "builder"); - env->CallVoidMethod(mScreenCaptureListenerObject, - gScreenCaptureListenerClassInfo.onScreenCaptureComplete, - screenshotHardwareBuffer); - checkAndClearException(env, "onScreenCaptureComplete"); + env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, screenshotHardwareBuffer); + checkAndClearException(env, "accept"); env->DeleteLocalRef(jhardwareBuffer); env->DeleteLocalRef(screenshotHardwareBuffer); return binder::Status::ok(); } private: - jobject mScreenCaptureListenerObject; + jobject mConsumerObject; JavaVM* mVm; JNIEnv* getenv() { @@ -194,7 +190,7 @@ static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env, } static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject, - jobject screenCaptureListenerObject) { + jlong screenCaptureListenerObject) { const DisplayCaptureArgs captureArgs = displayCaptureArgsFromObject(env, displayCaptureArgsObject); @@ -202,13 +198,13 @@ static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptu return BAD_VALUE; } - sp<IScreenCaptureListener> captureListener = - sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject); + sp<gui::IScreenCaptureListener> captureListener = + reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject); return ScreenshotClient::captureDisplay(captureArgs, captureListener); } static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject, - jobject screenCaptureListenerObject) { + jlong screenCaptureListenerObject) { LayerCaptureArgs captureArgs; getCaptureArgs(env, layerCaptureArgsObject, captureArgs); SurfaceControl* layer = reinterpret_cast<SurfaceControl*>( @@ -238,21 +234,70 @@ static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureA } } - sp<IScreenCaptureListener> captureListener = - sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject); + sp<gui::IScreenCaptureListener> captureListener = + reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject); return ScreenshotClient::captureLayers(captureArgs, captureListener); } +static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) { + sp<gui::IScreenCaptureListener> listener = + sp<ScreenCaptureListenerWrapper>::make(env, consumerObj); + listener->incStrong((void*)nativeCreateScreenCaptureListener); + return reinterpret_cast<jlong>(listener.get()); +} + +static void nativeWriteListenerToParcel(JNIEnv* env, jclass clazz, jlong nativeObject, + jobject parcelObj) { + Parcel* parcel = parcelForJavaObject(env, parcelObj); + if (parcel == NULL) { + jniThrowNullPointerException(env, NULL); + return; + } + ScreenCaptureListenerWrapper* const self = + reinterpret_cast<ScreenCaptureListenerWrapper*>(nativeObject); + if (self != nullptr) { + parcel->writeStrongBinder(IInterface::asBinder(self)); + } +} + +static jlong nativeReadListenerFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) { + Parcel* parcel = parcelForJavaObject(env, parcelObj); + if (parcel == NULL) { + jniThrowNullPointerException(env, NULL); + return 0; + } + sp<gui::IScreenCaptureListener> listener = + interface_cast<gui::IScreenCaptureListener>(parcel->readStrongBinder()); + if (listener == nullptr) { + return 0; + } + listener->incStrong((void*)nativeCreateScreenCaptureListener); + return reinterpret_cast<jlong>(listener.get()); +} + +void destroyNativeListener(void* ptr) { + ScreenCaptureListenerWrapper* listener = reinterpret_cast<ScreenCaptureListenerWrapper*>(ptr); + listener->decStrong((void*)nativeCreateScreenCaptureListener); +} + +static jlong getNativeListenerFinalizer(JNIEnv* env, jclass clazz) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeListener)); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod sScreenCaptureMethods[] = { // clang-format off - {"nativeCaptureDisplay", - "(Landroid/window/ScreenCapture$DisplayCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I", + {"nativeCaptureDisplay", "(Landroid/window/ScreenCapture$DisplayCaptureArgs;J)I", (void*)nativeCaptureDisplay }, - {"nativeCaptureLayers", - "(Landroid/window/ScreenCapture$LayerCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I", + {"nativeCaptureLayers", "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I", (void*)nativeCaptureLayers }, + {"nativeCreateScreenCaptureListener", "(Ljava/util/function/Consumer;)J", + (void*)nativeCreateScreenCaptureListener }, + {"nativeWriteListenerToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteListenerToParcel }, + {"nativeReadListenerFromParcel", "(Landroid/os/Parcel;)J", + (void*)nativeReadListenerFromParcel }, + {"getNativeListenerFinalizer", "()J", (void*)getNativeListenerFinalizer }, // clang-format on }; @@ -293,12 +338,8 @@ int register_android_window_ScreenCapture(JNIEnv* env) { gLayerCaptureArgsClassInfo.childrenOnly = GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z"); - jclass screenCaptureListenerClazz = - FindClassOrDie(env, "android/window/ScreenCapture$ScreenCaptureListener"); - gScreenCaptureListenerClassInfo.clazz = MakeGlobalRefOrDie(env, screenCaptureListenerClazz); - gScreenCaptureListenerClassInfo.onScreenCaptureComplete = - GetMethodIDOrDie(env, screenCaptureListenerClazz, "onScreenCaptureComplete", - "(Landroid/window/ScreenCapture$ScreenshotHardwareBuffer;)V"); + jclass consumer = FindClassOrDie(env, "java/util/function/Consumer"); + gConsumerClassInfo.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V"); jclass screenshotGraphicsBufferClazz = FindClassOrDie(env, "android/window/ScreenCapture$ScreenshotHardwareBuffer"); diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index dfada139b4fe..1d2ce7eb37c3 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4850,9 +4850,6 @@ --> </array> - <!-- How long it takes for the HW to start illuminating after the illumination is requested. --> - <integer name="config_udfps_illumination_transition_ms">50</integer> - <!-- Indicates whether device has a power button fingerprint sensor. --> <bool name="config_is_powerbutton_fps" translatable="false" >false</bool> @@ -5191,7 +5188,7 @@ <!-- Vertical position of a center of the letterboxed app window. 0 corresponds to the upper side of the screen and 1 to the lower side. If given value < 0 or > 1, it is ignored and central position is used (0.5). --> - <item name="config_letterboxVerticalPositionMultiplier" format="float" type="dimen">0.5</item> + <item name="config_letterboxVerticalPositionMultiplier" format="float" type="dimen">0.0</item> <!-- Whether horizontal reachability repositioning is allowed for letterboxed fullscreen apps. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7e8423a2d284..95a81e82a2f4 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2705,7 +2705,6 @@ <java-symbol type="bool" name="allow_test_udfps" /> <java-symbol type="array" name="config_udfps_sensor_props" /> <java-symbol type="array" name="config_sfps_sensor_props" /> - <java-symbol type="integer" name="config_udfps_illumination_transition_ms" /> <java-symbol type="bool" name="config_is_powerbutton_fps" /> <java-symbol type="array" name="config_udfps_enroll_stage_thresholds" /> <java-symbol type="array" name="config_sfps_enroll_stage_thresholds" /> diff --git a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java index ddc05e09c395..7338c3a3ea8a 100644 --- a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java +++ b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java @@ -204,19 +204,6 @@ public class AdaptiveIconDrawableTest extends AndroidTestCase { assertEquals(100, Color.alpha(bitmap.getPixel(50, 50))); } - @Test - public void testSetBounds() throws Exception { - mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable); - mIconDrawable.setBounds(0, 0, 100, 100); - - assertEquals(new Rect(-25, -25, 125, 125), mBackgroundDrawable.getBounds()); - assertEquals(new Rect(-25, -25, 125, 125), mForegroundDrawable.getBounds()); - - mIconDrawable.setBounds(10, 10, 110, 110); - assertEquals(new Rect(-15, -15, 135, 135), mBackgroundDrawable.getBounds()); - assertEquals(new Rect(-15, -15, 135, 135), mForegroundDrawable.getBounds()); - } - // // Utils // diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java index 4770fa6315aa..7674135719e9 100644 --- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java @@ -262,6 +262,8 @@ public class InteractionJankMonitorTest { && f.getType() == int.class) .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName)); + assertThat(enumsMap.size() - 1).isEqualTo(cujs.size()); + cujs.forEach(f -> { final int cuj = getIntFieldChecked(f); final String cujName = f.getName(); @@ -279,7 +281,9 @@ public class InteractionJankMonitorTest { .that(expectedEnumName.equals(enumName)) .isTrue(); mExpect - .withMessage(formatSimple("getNameOfCuj(%d) not matches %s", cuj, cujName)) + .withMessage( + formatSimple("getNameOfCuj(%d) not matches: %s, expected=%s", + cuj, cujName, expectedNameOfCuj)) .that(cujName.equals(expectedNameOfCuj)) .isTrue(); }); diff --git a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java new file mode 100644 index 000000000000..e384e699333b --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java @@ -0,0 +1,116 @@ +/* + * 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.internal.util; + +import static android.text.TextUtils.formatSimple; + +import static com.android.internal.util.LatencyTracker.STATSD_ACTION; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import androidx.test.filters.SmallTest; + +import com.google.common.truth.Expect; + +import org.junit.Rule; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@SmallTest +public class LatencyTrackerTest { + private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__"; + + @Rule + public final Expect mExpect = Expect.create(); + + @Test + public void testCujsMapToEnumsCorrectly() { + List<Field> actions = Arrays.stream(LatencyTracker.class.getDeclaredFields()) + .filter(f -> f.getName().startsWith("ACTION_") + && Modifier.isStatic(f.getModifiers()) + && f.getType() == int.class) + .collect(Collectors.toList()); + + Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields()) + .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX) + && Modifier.isStatic(f.getModifiers()) + && f.getType() == int.class) + .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName)); + + assertThat(enumsMap.size() - 1).isEqualTo(actions.size()); + + actions.forEach(f -> { + final int action = getIntFieldChecked(f); + final String actionName = f.getName(); + final String expectedEnumName = formatSimple("%s%s", ENUM_NAME_PREFIX, actionName); + final int enumKey = STATSD_ACTION[action]; + final String enumName = enumsMap.get(enumKey); + final String expectedActionName = LatencyTracker.getNameOfAction(enumKey); + mExpect + .withMessage(formatSimple( + "%s (%d) not matches %s (%d)", actionName, action, enumName, enumKey)) + .that(expectedEnumName.equals(enumName)) + .isTrue(); + mExpect + .withMessage( + formatSimple("getNameOfAction(%d) not matches: %s, expected=%s", + enumKey, actionName, expectedActionName)) + .that(actionName.equals(expectedActionName)) + .isTrue(); + }); + } + + @Test + public void testCujTypeEnumCorrectlyDefined() throws Exception { + List<Field> cujEnumFields = + Arrays.stream(LatencyTracker.class.getDeclaredFields()) + .filter(field -> field.getName().startsWith("ACTION_") + && Modifier.isStatic(field.getModifiers()) + && field.getType() == int.class) + .collect(Collectors.toList()); + + HashSet<Integer> allValues = new HashSet<>(); + for (Field field : cujEnumFields) { + int fieldValue = field.getInt(null); + assertWithMessage( + "Field %s must have a mapping to a value in STATSD_ACTION", + field.getName()) + .that(fieldValue < STATSD_ACTION.length) + .isTrue(); + assertWithMessage("All CujType values must be unique. Field %s repeats existing value.", + field.getName()) + .that(allValues.add(fieldValue)) + .isTrue(); + } + } + + private int getIntFieldChecked(Field field) { + try { + return field.getInt(null); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java index 47de37d76167..688425a77ab5 100644 --- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java +++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java @@ -26,6 +26,8 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.Color; @@ -37,6 +39,8 @@ import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Region; +import android.graphics.Shader; +import android.graphics.Shader.TileMode; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.PathParser; @@ -102,8 +106,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE); /** - * Unused path. - * TODO: Remove once the layoutLib is updated + * Clip path defined in R.string.config_icon_mask. */ private static Path sMask; @@ -111,10 +114,9 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback * Scaled mask based on the view bounds. */ private final Path mMask; - private final Path mMaskTransformed; + private final Path mMaskScaleOnly; private final Matrix mMaskMatrix; private final Region mTransparentRegion; - private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); /** * Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and @@ -129,10 +131,19 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback */ LayerState mLayerState; + private Shader mLayersShader; + private Bitmap mLayersBitmap; + private final Rect mTmpOutRect = new Rect(); private Rect mHotspotBounds; private boolean mMutated; + private boolean mSuspendChildInvalidation; + private boolean mChildRequestedInvalidation; + private final Canvas mCanvas; + private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | + Paint.FILTER_BITMAP_FLAG); + /** * Constructor used for xml inflation. */ @@ -146,16 +157,19 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback */ AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) { mLayerState = createConstantState(state, res); - // config_icon_mask from context bound resource may have been changed using + // config_icon_mask from context bound resource may have been chaged using // OverlayManager. Read that one first. Resources r = ActivityThread.currentActivityThread() == null ? Resources.getSystem() : ActivityThread.currentActivityThread().getApplication().getResources(); - mMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask)); - mMaskTransformed = new Path(); + // TODO: either make sMask update only when config_icon_mask changes OR + // get rid of it all-together in layoutlib + sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask)); + mMask = new Path(sMask); + mMaskScaleOnly = new Path(mMask); mMaskMatrix = new Matrix(); + mCanvas = new Canvas(); mTransparentRegion = new Region(); - mPaint.setColor(Color.BLACK); } private ChildDrawable createChildDrawable(Drawable drawable) { @@ -266,7 +280,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback * @return the mask path object used to clip the drawable */ public Path getIconMask() { - return mMaskTransformed; + return mMask; } /** @@ -308,47 +322,92 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback if (bounds.isEmpty()) { return; } - // Set the child layer bounds bigger than the view port size - // by {@link #DEFAULT_VIEW_PORT_SCALE} - float cX = bounds.exactCenterX(); - float cY = bounds.exactCenterY(); - float insetWidth = bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2); - float insetHeight = bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2); - final Rect outRect = mTmpOutRect; - outRect.set( - (int) (cX - insetWidth), - (int) (cY - insetHeight), - (int) (cX + insetWidth), - (int) (cY + insetHeight)); + updateLayerBounds(bounds); + } + + private void updateLayerBounds(Rect bounds) { + if (bounds.isEmpty()) { + return; + } + try { + suspendChildInvalidation(); + updateLayerBoundsInternal(bounds); + updateMaskBoundsInternal(bounds); + } finally { + resumeChildInvalidation(); + } + } + + /** + * Set the child layer bounds bigger than the view port size by {@link #DEFAULT_VIEW_PORT_SCALE} + */ + private void updateLayerBoundsInternal(Rect bounds) { + int cX = bounds.width() / 2; + int cY = bounds.height() / 2; for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) { final ChildDrawable r = mLayerState.mChildren[i]; - if (r.mDrawable != null) { - r.mDrawable.setBounds(outRect); + final Drawable d = r.mDrawable; + if (d == null) { + continue; } + + int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2)); + int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2)); + final Rect outRect = mTmpOutRect; + outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight); + + d.setBounds(outRect); } + } + + private void updateMaskBoundsInternal(Rect b) { + // reset everything that depends on the view bounds + mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE); + sMask.transform(mMaskMatrix, mMaskScaleOnly); - // Update the clipping mask - mMaskMatrix.setScale(bounds.width() / MASK_SIZE, bounds.height() / MASK_SIZE); - mMaskMatrix.postTranslate(bounds.left, bounds.top); - mMask.transform(mMaskMatrix, mMaskTransformed); + mMaskMatrix.postTranslate(b.left, b.top); + sMask.transform(mMaskMatrix, mMask); - // Clear the transparent region, it is calculated lazily + if (mLayersBitmap == null || mLayersBitmap.getWidth() != b.width() + || mLayersBitmap.getHeight() != b.height()) { + mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888); + } + + mPaint.setShader(null); mTransparentRegion.setEmpty(); + mLayersShader = null; } @Override public void draw(Canvas canvas) { - int saveCount = canvas.save(); - canvas.clipPath(mMaskTransformed); - canvas.drawPaint(mPaint); - if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) { - mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(canvas); + if (mLayersBitmap == null) { + return; } - if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) { - mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(canvas); + if (mLayersShader == null) { + mCanvas.setBitmap(mLayersBitmap); + mCanvas.drawColor(Color.BLACK); + if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) { + mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(mCanvas); + } + if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) { + mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(mCanvas); + } + mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP); + mPaint.setShader(mLayersShader); + } + if (mMaskScaleOnly != null) { + Rect bounds = getBounds(); + canvas.translate(bounds.left, bounds.top); + canvas.drawPath(mMaskScaleOnly, mPaint); + canvas.translate(-bounds.left, -bounds.top); } - canvas.restoreToCount(saveCount); + } + + @Override + public void invalidateSelf() { + mLayersShader = null; + super.invalidateSelf(); } @Override @@ -541,9 +600,37 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback return false; } + /** + * Temporarily suspends child invalidation. + * + * @see #resumeChildInvalidation() + */ + private void suspendChildInvalidation() { + mSuspendChildInvalidation = true; + } + + /** + * Resumes child invalidation after suspension, immediately performing an + * invalidation if one was requested by a child during suspension. + * + * @see #suspendChildInvalidation() + */ + private void resumeChildInvalidation() { + mSuspendChildInvalidation = false; + + if (mChildRequestedInvalidation) { + mChildRequestedInvalidation = false; + invalidateSelf(); + } + } + @Override public void invalidateDrawable(@NonNull Drawable who) { - invalidateSelf(); + if (mSuspendChildInvalidation) { + mChildRequestedInvalidation = true; + } else { + invalidateSelf(); + } } @Override @@ -627,13 +714,6 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); - final ChildDrawable[] array = mLayerState.mChildren; - for (int i = 0; i < mLayerState.N_CHILDREN; i++) { - final Drawable dr = array[i].mDrawable; - if (dr != null) { - dr.setAlpha(alpha); - } - } } @Override @@ -736,6 +816,10 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback } } + if (changed) { + updateLayerBounds(getBounds()); + } + return changed; } @@ -751,6 +835,10 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback } } + if (changed) { + updateLayerBounds(getBounds()); + } + return changed; } @@ -891,7 +979,6 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback int mDensity; // The density to use when inflating/looking up the children drawables. A value of 0 means - // use the system's density. int mSrcDensityOverride = 0; diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index d9a7994d6c4a..dbd918e35d70 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -29,6 +29,8 @@ import libcore.util.EmptyArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; import java.util.Collection; import java.util.Locale; @@ -675,6 +677,26 @@ public abstract class KeyProperties { } } + /** + * @hide + */ + @NonNull public static @DigestEnum + AlgorithmParameterSpec fromKeymasterToMGF1ParameterSpec(int digest) { + switch (digest) { + default: + case KeymasterDefs.KM_DIGEST_SHA1: + return MGF1ParameterSpec.SHA1; + case KeymasterDefs.KM_DIGEST_SHA_2_224: + return MGF1ParameterSpec.SHA224; + case KeymasterDefs.KM_DIGEST_SHA_2_256: + return MGF1ParameterSpec.SHA256; + case KeymasterDefs.KM_DIGEST_SHA_2_384: + return MGF1ParameterSpec.SHA384; + case KeymasterDefs.KM_DIGEST_SHA_2_512: + return MGF1ParameterSpec.SHA512; + } + } + @NonNull public static @DigestEnum String fromKeymasterToSignatureAlgorithmDigest(int digest) { switch (digest) { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java index e808c5cc51bd..7571e44a7713 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java @@ -69,6 +69,7 @@ import javax.crypto.spec.SecretKeySpec; */ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation { private static final String TAG = "AndroidKeyStoreCipherSpiBase"; + public static final String DEFAULT_MGF1_DIGEST = "SHA-1"; // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after // doFinal finishes. @@ -133,24 +134,28 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor if ("RSA/ECB/OAEPWithSHA-224AndMGF1Padding".equals(transform)) { OAEPParameterSpec spec = new OAEPParameterSpec("SHA-224", "MGF1", - new MGF1ParameterSpec("SHA1"), PSource.PSpecified.DEFAULT); + new MGF1ParameterSpec(DEFAULT_MGF1_DIGEST), + PSource.PSpecified.DEFAULT); mCipher.init(opmode, key, spec, random); } else if ("RSA/ECB/OAEPWithSHA-256AndMGF1Padding".equals(transform)) { OAEPParameterSpec spec = new OAEPParameterSpec("SHA-256", "MGF1", - new MGF1ParameterSpec("SHA1"), PSource.PSpecified.DEFAULT); + new MGF1ParameterSpec(DEFAULT_MGF1_DIGEST), + PSource.PSpecified.DEFAULT); mCipher.init(opmode, key, spec, random); } else if ("RSA/ECB/OAEPWithSHA-384AndMGF1Padding".equals(transform)) { OAEPParameterSpec spec = new OAEPParameterSpec("SHA-384", "MGF1", - new MGF1ParameterSpec("SHA1"), PSource.PSpecified.DEFAULT); + new MGF1ParameterSpec(DEFAULT_MGF1_DIGEST), + PSource.PSpecified.DEFAULT); mCipher.init(opmode, key, spec, random); } else if ("RSA/ECB/OAEPWithSHA-512AndMGF1Padding".equals(transform)) { OAEPParameterSpec spec = new OAEPParameterSpec("SHA-512", "MGF1", - new MGF1ParameterSpec("SHA1"), PSource.PSpecified.DEFAULT); + new MGF1ParameterSpec(DEFAULT_MGF1_DIGEST), + PSource.PSpecified.DEFAULT); mCipher.init(opmode, key, spec, random); } else { mCipher.init(opmode, key, random); diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index cdc1085a5015..acc0005154b4 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -16,6 +16,8 @@ package android.security.keystore2; +import static android.security.keystore2.AndroidKeyStoreCipherSpiBase.DEFAULT_MGF1_DIGEST; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; @@ -908,6 +910,26 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato params.add(KeyStore2ParameterUtils.makeEnum( KeymasterDefs.KM_TAG_PADDING, padding )); + if (padding == KeymasterDefs.KM_PAD_RSA_OAEP) { + final boolean[] hasDefaultMgf1DigestBeenAdded = {false}; + ArrayUtils.forEach(mKeymasterDigests, (digest) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, digest + )); + hasDefaultMgf1DigestBeenAdded[0] |= + digest.equals(KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)); + }); + /* Because of default MGF1 digest is SHA-1. It has to be added in Key + * characteristics. Otherwise, crypto operations will fail with Incompatible + * MGF1 digest. + */ + if (!hasDefaultMgf1DigestBeenAdded[0]) { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, + KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST) + )); + } + } }); ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> { params.add(KeyStore2ParameterUtils.makeEnum( diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java index 5848247809e7..e9b66aafc262 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java @@ -161,10 +161,11 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase */ abstract static class OAEPWithMGF1Padding extends AndroidKeyStoreRSACipherSpi { - private static final String MGF_ALGORITGM_MGF1 = "MGF1"; + private static final String MGF_ALGORITHM_MGF1 = "MGF1"; private int mKeymasterDigest = -1; private int mDigestOutputSizeBytes; + private int mKeymasterMgf1Digest = KeymasterDefs.KM_DIGEST_SHA1; // Default MGF1 digest OAEPWithMGF1Padding(int keymasterDigest) { super(KeymasterDefs.KM_PAD_RSA_OAEP); @@ -189,10 +190,10 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase + ". Only OAEPParameterSpec supported"); } OAEPParameterSpec spec = (OAEPParameterSpec) params; - if (!MGF_ALGORITGM_MGF1.equalsIgnoreCase(spec.getMGFAlgorithm())) { + if (!MGF_ALGORITHM_MGF1.equalsIgnoreCase(spec.getMGFAlgorithm())) { throw new InvalidAlgorithmParameterException( "Unsupported MGF: " + spec.getMGFAlgorithm() - + ". Only " + MGF_ALGORITGM_MGF1 + " supported"); + + ". Only " + MGF_ALGORITHM_MGF1 + " supported"); } String jcaDigest = spec.getDigestAlgorithm(); int keymasterDigest; @@ -225,11 +226,6 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase } MGF1ParameterSpec mgfSpec = (MGF1ParameterSpec) mgfParams; String mgf1JcaDigest = mgfSpec.getDigestAlgorithm(); - if (!KeyProperties.DIGEST_SHA1.equalsIgnoreCase(mgf1JcaDigest)) { - throw new InvalidAlgorithmParameterException( - "Unsupported MGF1 digest: " + mgf1JcaDigest - + ". Only " + KeyProperties.DIGEST_SHA1 + " supported"); - } PSource pSource = spec.getPSource(); if (!(pSource instanceof PSource.PSpecified)) { throw new InvalidAlgorithmParameterException( @@ -244,6 +240,7 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase + ". Only pSpecifiedEmpty (PSource.PSpecified.DEFAULT) supported"); } mKeymasterDigest = keymasterDigest; + mKeymasterMgf1Digest = KeyProperties.Digest.toKeymaster(mgf1JcaDigest); mDigestOutputSizeBytes = (KeymasterUtils.getDigestOutputSizeBits(keymasterDigest) + 7) / 8; } @@ -273,10 +270,10 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase protected final AlgorithmParameters engineGetParameters() { OAEPParameterSpec spec = new OAEPParameterSpec( - KeyProperties.Digest.fromKeymaster(mKeymasterDigest), - MGF_ALGORITGM_MGF1, - MGF1ParameterSpec.SHA1, - PSource.PSpecified.DEFAULT); + KeyProperties.Digest.fromKeymaster(mKeymasterDigest), + MGF_ALGORITHM_MGF1, + KeyProperties.Digest.fromKeymasterToMGF1ParameterSpec(mKeymasterMgf1Digest), + PSource.PSpecified.DEFAULT); try { AlgorithmParameters params = AlgorithmParameters.getInstance("OAEP"); params.init(spec); @@ -298,6 +295,9 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase parameters.add(KeyStore2ParameterUtils.makeEnum( KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mKeymasterMgf1Digest + )); } @Override diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java index dfda356ec35b..94bf122877bd 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -16,6 +16,8 @@ package android.security.keystore2; +import static android.security.keystore2.AndroidKeyStoreCipherSpiBase.DEFAULT_MGF1_DIGEST; + import android.annotation.NonNull; import android.hardware.biometrics.BiometricManager; import android.hardware.security.keymint.HardwareAuthenticatorType; @@ -511,6 +513,28 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { KeymasterDefs.KM_TAG_PADDING, padding )); + if (padding == KeymasterDefs.KM_PAD_RSA_OAEP) { + if (spec.isDigestsSpecified()) { + boolean hasDefaultMgf1DigestBeenAdded = false; + for (String digest : spec.getDigests()) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, + KeyProperties.Digest.toKeymaster(digest) + )); + hasDefaultMgf1DigestBeenAdded |= digest.equals(DEFAULT_MGF1_DIGEST); + } + /* Because of default MGF1 digest is SHA-1. It has to be added in Key + * characteristics. Otherwise, crypto operations will fail with Incompatible + * MGF1 digest. + */ + if (!hasDefaultMgf1DigestBeenAdded) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, + KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST) + )); + } + } + } } for (String padding : spec.getSignaturePaddings()) { importArgs.add(KeyStore2ParameterUtils.makeEnum( diff --git a/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java index dcdd7defd752..54955c6b7fab 100644 --- a/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java +++ b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java @@ -78,6 +78,7 @@ public abstract class KeyStore2ParameterUtils { kp.value = KeyParameterValue.blockMode(v); break; case Tag.DIGEST: + case Tag.RSA_OAEP_MGF_DIGEST: kp.value = KeyParameterValue.digest(v); break; case Tag.EC_CURVE: diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java index a8764e05c3e2..d76ad3d27c70 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java @@ -517,7 +517,9 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, getViewTreeObserver().removeOnComputeInternalInsetsListener(this); } - ActivityManager.RunningTaskInfo getTaskInfo() { + /** Returns the task info for the task in the TaskView. */ + @Nullable + public ActivityManager.RunningTaskInfo getTaskInfo() { return mTaskInfo; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index c39602032170..7c3c14e2210c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -55,6 +55,8 @@ import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; import com.android.wm.shell.compatui.CompatUIController; +import com.android.wm.shell.desktopmode.DesktopMode; +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.draganddrop.DragAndDropController; @@ -477,11 +479,12 @@ public abstract class WMShellBaseModule { ShellInit shellInit, ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, + Optional<DesktopModeTaskRepository> desktopModeTaskRepository, @ShellMainThread ShellExecutor mainExecutor ) { return Optional.ofNullable( RecentTasksController.create(context, shellInit, shellCommandHandler, - taskStackListener, mainExecutor)); + taskStackListener, desktopModeTaskRepository, mainExecutor)); } // @@ -666,6 +669,24 @@ public abstract class WMShellBaseModule { } // + // Desktop mode (optional feature) + // + + @BindsOptionalOf + @DynamicOverride + abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository(); + + @WMSingleton + @Provides + static Optional<DesktopModeTaskRepository> providesDesktopModeTaskRepository( + @DynamicOverride Optional<DesktopModeTaskRepository> desktopModeTaskRepository) { + if (DesktopMode.IS_SUPPORTED) { + return desktopModeTaskRepository; + } + return Optional.empty(); + } + + // // Misc // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 18ce3642335d..27d3e35fa07a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -51,6 +51,7 @@ import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.desktopmode.DesktopModeController; +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformTaskListener; @@ -220,14 +221,14 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, - Optional<RecentTasksController> recentTasksController, + Optional<DesktopModeTaskRepository> desktopModeTaskRepository, WindowDecorViewModel<?> windowDecorViewModel) { // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic // override for this controller from the base module ShellInit init = FreeformComponents.isFreeformEnabled(context) ? shellInit : null; - return new FreeformTaskListener<>(init, shellTaskOrganizer, recentTasksController, + return new FreeformTaskListener<>(init, shellTaskOrganizer, desktopModeTaskRepository, windowDecorViewModel); } @@ -599,17 +600,24 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, RootDisplayAreaOrganizer rootDisplayAreaOrganizer, - @ShellMainThread Handler mainHandler + @ShellMainThread Handler mainHandler, + Transitions transitions ) { if (DesktopMode.IS_SUPPORTED) { return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer, - rootDisplayAreaOrganizer, - mainHandler)); + rootDisplayAreaOrganizer, mainHandler, transitions)); } else { return Optional.empty(); } } + @WMSingleton + @Provides + @DynamicOverride + static DesktopModeTaskRepository provideDesktopModeTaskRepository() { + return new DesktopModeTaskRepository(); + } + // // Misc // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java index 7d34ea481de6..c07ce1065302 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java @@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.TRANSIT_CHANGE; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; @@ -37,6 +38,7 @@ import com.android.wm.shell.RootDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; /** * Handles windowing changes when desktop mode system setting changes @@ -47,15 +49,18 @@ public class DesktopModeController { private final ShellTaskOrganizer mShellTaskOrganizer; private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer; private final SettingsObserver mSettingsObserver; + private final Transitions mTransitions; public DesktopModeController(Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, RootDisplayAreaOrganizer rootDisplayAreaOrganizer, - @ShellMainThread Handler mainHandler) { + @ShellMainThread Handler mainHandler, + Transitions transitions) { mContext = context; mShellTaskOrganizer = shellTaskOrganizer; mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer; mSettingsObserver = new SettingsObserver(mContext, mainHandler); + mTransitions = transitions; shellInit.addInitCallback(this::onInit, this); } @@ -89,7 +94,11 @@ public class DesktopModeController { } wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId, targetWindowingMode), true /* transfer */); - mRootDisplayAreaOrganizer.applyTransaction(wct); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mTransitions.startTransition(TRANSIT_CHANGE, wct, null); + } else { + mRootDisplayAreaOrganizer.applyTransaction(wct); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt new file mode 100644 index 000000000000..988601c0e8a8 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode + +import android.util.ArraySet + +/** + * Keeps track of task data related to desktop mode. + */ +class DesktopModeTaskRepository { + + /** + * Set of task ids that are marked as active in desktop mode. + * Active tasks in desktop mode are freeform tasks that are visible or have been visible after + * desktop mode was activated. + * Task gets removed from this list when it vanishes. Or when desktop mode is turned off. + */ + private val activeTasks = ArraySet<Int>() + private val listeners = ArraySet<Listener>() + + /** + * Add a [Listener] to be notified of updates to the repository. + */ + fun addListener(listener: Listener) { + listeners.add(listener) + } + + /** + * Remove a previously registered [Listener] + */ + fun removeListener(listener: Listener) { + listeners.remove(listener) + } + + /** + * Mark a task with given [taskId] as active. + */ + fun addActiveTask(taskId: Int) { + val added = activeTasks.add(taskId) + if (added) { + listeners.onEach { it.onActiveTasksChanged() } + } + } + + /** + * Remove task with given [taskId] from active tasks. + */ + fun removeActiveTask(taskId: Int) { + val removed = activeTasks.remove(taskId) + if (removed) { + listeners.onEach { it.onActiveTasksChanged() } + } + } + + /** + * Check if a task with the given [taskId] was marked as an active task + */ + fun isActiveTask(taskId: Int): Boolean { + return activeTasks.contains(taskId) + } + + /** + * Get a set of the active tasks + */ + fun getActiveTasks(): ArraySet<Int> { + return ArraySet(activeTasks) + } + + /** + * Defines interface for classes that can listen to changes in repository state. + */ + interface Listener { + fun onActiveTasksChanged() + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index 435d8eaa563e..62bf5172e106 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -17,6 +17,8 @@ package com.android.wm.shell.draganddrop; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED; +import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -263,6 +265,9 @@ public class DragAndDropPolicy { mStarter.startShortcut(packageName, id, position, opts, user); } else { final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT); + // Put BAL flags to avoid activity start aborted. + opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true); + opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true); mStarter.startIntent(launchIntent, null /* fillIntent */, position, opts); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 1baac718ee95..f58719b225a4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -29,8 +29,8 @@ import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.desktopmode.DesktopMode; +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -49,7 +49,7 @@ public class FreeformTaskListener<T extends AutoCloseable> private static final String TAG = "FreeformTaskListener"; private final ShellTaskOrganizer mShellTaskOrganizer; - private final Optional<RecentTasksController> mRecentTasksOptional; + private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository; private final WindowDecorViewModel<T> mWindowDecorationViewModel; private final SparseArray<State<T>> mTasks = new SparseArray<>(); @@ -64,11 +64,11 @@ public class FreeformTaskListener<T extends AutoCloseable> public FreeformTaskListener( ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, - Optional<RecentTasksController> recentTasksController, + Optional<DesktopModeTaskRepository> desktopModeTaskRepository, WindowDecorViewModel<T> windowDecorationViewModel) { mShellTaskOrganizer = shellTaskOrganizer; mWindowDecorationViewModel = windowDecorationViewModel; - mRecentTasksOptional = recentTasksController; + mDesktopModeTaskRepository = desktopModeTaskRepository; if (shellInit != null) { shellInit.addInitCallback(this::onInit, this); } @@ -93,7 +93,7 @@ public class FreeformTaskListener<T extends AutoCloseable> if (DesktopMode.IS_SUPPORTED && taskInfo.isVisible) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Adding active freeform task: #%d", taskInfo.taskId); - mRecentTasksOptional.ifPresent(rt -> rt.addActiveFreeformTask(taskInfo.taskId)); + mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId)); } } @@ -126,7 +126,7 @@ public class FreeformTaskListener<T extends AutoCloseable> if (DesktopMode.IS_SUPPORTED) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Removing active freeform task: #%d", taskInfo.taskId); - mRecentTasksOptional.ifPresent(rt -> rt.removeActiveFreeformTask(taskInfo.taskId)); + mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId)); } if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -154,7 +154,7 @@ public class FreeformTaskListener<T extends AutoCloseable> if (taskInfo.isVisible) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Adding active freeform task: #%d", taskInfo.taskId); - mRecentTasksOptional.ifPresent(rt -> rt.addActiveFreeformTask(taskInfo.taskId)); + mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId)); } } } @@ -191,15 +191,19 @@ public class FreeformTaskListener<T extends AutoCloseable> * * @param change the change of this task transition that needs to have the task layer as the * leash - * @return {@code true} if it adopts the window decoration; {@code false} otherwise + * @return {@code true} if it creates the window decoration; {@code false} otherwise */ - void createWindowDecoration( + boolean createWindowDecoration( TransitionInfo.Change change, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash()); + if (state.mWindowDecoration != null) { + return false; + } state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration( state.mTaskInfo, state.mLeash, startT, finishT); + return true; } /** @@ -222,6 +226,9 @@ public class FreeformTaskListener<T extends AutoCloseable> windowDecor = mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId); } + if (windowDecor == null) { + return null; + } mWindowDecorationViewModel.setupWindowDecorationForTransition( taskInfo, startT, finishT, windowDecor); return windowDecor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java index a780ec102ea9..17d60671e964 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java @@ -26,6 +26,7 @@ import android.util.Log; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; +import android.window.WindowContainerToken; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -80,6 +81,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT) { final ArrayList<AutoCloseable> windowDecors = new ArrayList<>(); + final ArrayList<WindowContainerToken> taskParents = new ArrayList<>(); for (TransitionInfo.Change change : info.getChanges()) { if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) { continue; @@ -89,9 +91,22 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs if (taskInfo == null || taskInfo.taskId == -1) { continue; } + // Filter out non-leaf tasks. Freeform/fullscreen don't nest tasks, but split-screen + // does, so this prevents adding duplicate captions in that scenario. + if (change.getParent() != null + && info.getChange(change.getParent()).getTaskInfo() != null) { + // This logic relies on 2 assumptions: 1 is that child tasks will be visited before + // parents (due to how z-order works). 2 is that no non-tasks are interleaved + // between tasks (hierarchically). + taskParents.add(change.getContainer()); + } + if (taskParents.contains(change.getContainer())) { + continue; + } switch (change.getMode()) { case WindowManager.TRANSIT_OPEN: + case WindowManager.TRANSIT_TO_FRONT: onOpenTransitionReady(change, startT, finishT); break; case WindowManager.TRANSIT_CLOSE: { @@ -154,20 +169,28 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs boolean adopted = false; final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (type == Transitions.TRANSIT_MAXIMIZE - && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { + if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { windowDecor = mFreeformTaskListener.giveWindowDecoration( change.getTaskInfo(), startT, finishT); - adopted = mFullscreenTaskListener.adoptWindowDecoration( - change, startT, finishT, windowDecor); + if (windowDecor != null) { + adopted = mFullscreenTaskListener.adoptWindowDecoration( + change, startT, finishT, windowDecor); + } else { + // will return false if it already has the window decor. + adopted = mFullscreenTaskListener.createWindowDecoration(change, startT, finishT); + } } - if (type == Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE - && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { + if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { windowDecor = mFullscreenTaskListener.giveWindowDecoration( change.getTaskInfo(), startT, finishT); - adopted = mFreeformTaskListener.adoptWindowDecoration( - change, startT, finishT, windowDecor); + if (windowDecor != null) { + adopted = mFreeformTaskListener.adoptWindowDecoration( + change, startT, finishT, windowDecor); + } else { + // will return false if it already has the window decor. + adopted = mFreeformTaskListener.createWindowDecoration(change, startT, finishT); + } } if (!adopted) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java index e9f9bb5d7327..76e296bb8c61 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java @@ -16,8 +16,6 @@ package com.android.wm.shell.fullscreen; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; - import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString; @@ -107,13 +105,14 @@ public class FullscreenTaskListener<T extends AutoCloseable> if (Transitions.ENABLE_SHELL_TRANSITIONS) return; updateRecentsForVisibleFullscreenTask(taskInfo); - if (shouldShowWindowDecor(taskInfo) && mWindowDecorViewModelOptional.isPresent()) { + if (mWindowDecorViewModelOptional.isPresent()) { SurfaceControl.Transaction t = new SurfaceControl.Transaction(); state.mWindowDecoration = mWindowDecorViewModelOptional.get().createWindowDecoration(taskInfo, leash, t, t); t.apply(); - } else { + } + if (state.mWindowDecoration == null) { mSyncQueue.runInSync(t -> { // Reset several properties back to fullscreen (PiP, for example, leaves all these // properties in a bad state). @@ -174,17 +173,23 @@ public class FullscreenTaskListener<T extends AutoCloseable> * * @param change the change of this task transition that needs to have the task layer as the * leash + * @return {@code true} if a decoration was actually created. */ - public void createWindowDecoration(TransitionInfo.Change change, + public boolean createWindowDecoration(TransitionInfo.Change change, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash()); - if (!mWindowDecorViewModelOptional.isPresent() - || !shouldShowWindowDecor(state.mTaskInfo)) { - return; + if (!mWindowDecorViewModelOptional.isPresent()) return false; + if (state.mWindowDecoration != null) { + // Already has a decoration. + return false; } - - state.mWindowDecoration = mWindowDecorViewModelOptional.get().createWindowDecoration( + T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration( state.mTaskInfo, state.mLeash, startT, finishT); + if (newWindowDecor != null) { + state.mWindowDecoration = newWindowDecor; + return true; + } + return false; } /** @@ -202,8 +207,7 @@ public class FullscreenTaskListener<T extends AutoCloseable> SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, @Nullable AutoCloseable windowDecor) { - if (!mWindowDecorViewModelOptional.isPresent() - || !shouldShowWindowDecor(change.getTaskInfo())) { + if (!mWindowDecorViewModelOptional.isPresent()) { return false; } final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash()); @@ -214,8 +218,11 @@ public class FullscreenTaskListener<T extends AutoCloseable> state.mTaskInfo, startT, finishT, state.mWindowDecoration); return true; } else { - state.mWindowDecoration = mWindowDecorViewModelOptional.get().createWindowDecoration( + T newWindowDecor = mWindowDecorViewModelOptional.get().createWindowDecoration( state.mTaskInfo, state.mLeash, startT, finishT); + if (newWindowDecor != null) { + state.mWindowDecoration = newWindowDecor; + } return false; } } @@ -256,7 +263,7 @@ public class FullscreenTaskListener<T extends AutoCloseable> windowDecor = mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId); } - if (mWindowDecorViewModelOptional.isPresent()) { + if (mWindowDecorViewModelOptional.isPresent() && windowDecor != null) { mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition( taskInfo, startT, finishT, windowDecor); } @@ -336,10 +343,5 @@ public class FullscreenTaskListener<T extends AutoCloseable> return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN); } - private static boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { - return taskInfo.getConfiguration().windowConfiguration.getDisplayWindowingMode() - == WINDOWING_MODE_FREEFORM; - } - } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index f170e774739f..1a52d8c395ba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -324,6 +324,19 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return mPipTransitionController; } + /** + * Returns true if the PiP window is currently being animated. + */ + public boolean isAnimating() { + // TODO(b/183746978) move this to PipAnimationController, and inject that in PipController + PipAnimationController.PipTransitionAnimator animator = + mPipAnimationController.getCurrentAnimator(); + if (animator != null && animator.isRunning()) { + return true; + } + return false; + } + public Rect getCurrentOrAnimatingBounds() { PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getCurrentAnimator(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java index 6dd02e46d657..84071e08d472 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java @@ -54,13 +54,8 @@ public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithm { ? pipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas() : pipBoundsState.getBounds(); float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds); - int verticalGravity; + int verticalGravity = Gravity.BOTTOM; int horizontalGravity; - if (snapFraction < 1.5f || snapFraction >= 3.5f) { - verticalGravity = Gravity.NO_GRAVITY; - } else { - verticalGravity = Gravity.BOTTOM; - } if (snapFraction >= 0.5f && snapFraction < 2.5f) { horizontalGravity = Gravity.RIGHT; } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 3d879b685706..bc8191d2af46 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -149,7 +149,42 @@ public class PipController implements PipTransitionController.PipTransitionCallb private final Rect mTmpInsetBounds = new Rect(); private final int mEnterAnimationDuration; - private final Runnable mMovePipInResponseToKeepClearAreasChangeCallback; + private final Runnable mMovePipInResponseToKeepClearAreasChangeCallback = + this::onKeepClearAreasChangedCallback; + + private void onKeepClearAreasChangedCallback() { + if (!mEnablePipKeepClearAlgorithm) { + // early bail out if the keep clear areas feature is disabled + return; + } + // if there is another animation ongoing, wait for it to finish and try again + if (mPipTaskOrganizer.isAnimating()) { + mMainExecutor.removeCallbacks( + mMovePipInResponseToKeepClearAreasChangeCallback); + mMainExecutor.executeDelayed( + mMovePipInResponseToKeepClearAreasChangeCallback, + PIP_KEEP_CLEAR_AREAS_DELAY); + return; + } + updatePipPositionForKeepClearAreas(); + } + + private void updatePipPositionForKeepClearAreas() { + if (!mEnablePipKeepClearAlgorithm) { + // early bail out if the keep clear areas feature is disabled + return; + } + // only move if already in pip, other transitions account for keep clear areas + if (mPipTransitionState.hasEnteredPip()) { + Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState, + mPipBoundsAlgorithm); + // only move if the bounds are actually different + if (destBounds != mPipBoundsState.getBounds()) { + mPipTaskOrganizer.scheduleAnimateResizePip(destBounds, + mEnterAnimationDuration, null); + } + } + } private boolean mIsInFixedRotation; private PipAnimationListener mPinnedStackAnimationRecentsCallback; @@ -302,6 +337,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { mPipBoundsState.setImeVisibility(imeVisible, imeHeight); mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight); + if (imeVisible) { + updatePipPositionForKeepClearAreas(); + } } @Override @@ -414,15 +452,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb mEnterAnimationDuration = mContext.getResources() .getInteger(R.integer.config_pipEnterAnimationDuration); - mMovePipInResponseToKeepClearAreasChangeCallback = () -> { - // only move if already in pip, other transitions account for keep clear areas - if (mPipTransitionState.hasEnteredPip()) { - Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState, - mPipBoundsAlgorithm); - mPipTaskOrganizer.scheduleAnimateResizePip(destBounds, - mEnterAnimationDuration, null); - } - }; mPipParamsChangedForwarder = pipParamsChangedForwarder; mDisplayInsetsController = displayInsetsController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 84d9217e6fb3..1f3f31e025a0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -34,6 +34,7 @@ import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; +import android.os.SystemProperties; import android.provider.DeviceConfig; import android.util.Size; import android.view.DisplayCutout; @@ -70,6 +71,9 @@ public class PipTouchHandler { private static final String TAG = "PipTouchHandler"; private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f; + private static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM = + SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", false); + // Allow PIP to resize to a slightly bigger state upon touch private boolean mEnableResize; private final Context mContext; @@ -426,6 +430,9 @@ public class PipTouchHandler { if (mTouchState.isUserInteracting()) { // Defer the update of the current movement bounds until after the user finishes // touching the screen + } else if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) { + // Ignore moving PiP if keep clear algorithm is enabled, since IME and shelf height + // now are accounted for in the keep clear algorithm calculations } else { final boolean isExpanded = mMenuState == MENU_STATE_FULL && willResizeMenu(); final Rect toMovementBounds = new Rect(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 27bc1a189086..ff4b2edb7310 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -44,6 +44,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.desktopmode.DesktopMode; +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; @@ -53,19 +54,20 @@ import com.android.wm.shell.util.SplitBounds; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; /** * Manages the recent task list from the system, caching it as necessary. */ public class RecentTasksController implements TaskStackListenerCallback, - RemoteCallable<RecentTasksController> { + RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.Listener { private static final String TAG = RecentTasksController.class.getSimpleName(); private final Context mContext; private final ShellCommandHandler mShellCommandHandler; + private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository; private final ShellExecutor mMainExecutor; private final TaskStackListenerImpl mTaskStackListener; private final RecentTasks mImpl = new RecentTasksImpl(); @@ -84,15 +86,6 @@ public class RecentTasksController implements TaskStackListenerCallback, private final Map<Integer, SplitBounds> mTaskSplitBoundsMap = new HashMap<>(); /** - * Set of taskId's that have been launched in freeform mode. - * This includes tasks that are currently running, visible and in freeform mode. And also - * includes tasks that are running in the background, are no longer visible, but at some point - * were visible to the user. - * This is used to decide which freeform apps belong to the user's desktop. - */ - private final HashSet<Integer> mActiveFreeformTasks = new HashSet<>(); - - /** * Creates {@link RecentTasksController}, returns {@code null} if the feature is not * supported. */ @@ -102,24 +95,27 @@ public class RecentTasksController implements TaskStackListenerCallback, ShellInit shellInit, ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, + Optional<DesktopModeTaskRepository> desktopModeTaskRepository, @ShellMainThread ShellExecutor mainExecutor ) { if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) { return null; } return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener, - mainExecutor); + desktopModeTaskRepository, mainExecutor); } RecentTasksController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, + Optional<DesktopModeTaskRepository> desktopModeTaskRepository, ShellExecutor mainExecutor) { mContext = context; mShellCommandHandler = shellCommandHandler; mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC); mTaskStackListener = taskStackListener; + mDesktopModeTaskRepository = desktopModeTaskRepository; mMainExecutor = mainExecutor; shellInit.addInitCallback(this::onInit, this); } @@ -131,6 +127,7 @@ public class RecentTasksController implements TaskStackListenerCallback, private void onInit() { mShellCommandHandler.addDumpCallback(this::dump, this); mTaskStackListener.addListener(this); + mDesktopModeTaskRepository.ifPresent(it -> it.addListener(this)); } /** @@ -217,19 +214,8 @@ public class RecentTasksController implements TaskStackListenerCallback, notifyRecentTasksChanged(); } - /** - * Mark a task with given {@code taskId} as active in freeform - */ - public void addActiveFreeformTask(int taskId) { - mActiveFreeformTasks.add(taskId); - notifyRecentTasksChanged(); - } - - /** - * Remove task with given {@code taskId} from active freeform tasks - */ - public void removeActiveFreeformTask(int taskId) { - mActiveFreeformTasks.remove(taskId); + @Override + public void onActiveTasksChanged() { notifyRecentTasksChanged(); } @@ -312,7 +298,8 @@ public class RecentTasksController implements TaskStackListenerCallback, continue; } - if (desktopModeActive && mActiveFreeformTasks.contains(taskInfo.taskId)) { + if (desktopModeActive && mDesktopModeTaskRepository.isPresent() + && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) { // Freeform tasks will be added as a separate entry freeformTasks.add(taskInfo); continue; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 025e5592e367..3758471664cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -154,7 +154,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private StageCoordinator mStageCoordinator; // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated // outside the bounds of the roots by being reparented into a higher level fullscreen container - private SurfaceControl mSplitTasksContainerLayer; + private SurfaceControl mGoingToRecentsTasksLayer; + private SurfaceControl mStartingSplitTasksLayer; public SplitScreenController(Context context, ShellInit shellInit, @@ -509,19 +510,53 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) { + if (ENABLE_SHELL_TRANSITIONS) return null; + if (isSplitScreenVisible()) { // Evict child tasks except the top visible one under split root to ensure it could be // launched as full screen when switching to it on recents. final WindowContainerTransaction wct = new WindowContainerTransaction(); mStageCoordinator.prepareEvictInvisibleChildTasks(wct); mSyncQueue.queue(wct); + } else { + return null; } - return reparentSplitTasksForAnimation(apps, false /* enterSplitScreen */); + + SurfaceControl.Transaction t = mTransactionPool.acquire(); + if (mGoingToRecentsTasksLayer != null) { + t.remove(mGoingToRecentsTasksLayer); + } + mGoingToRecentsTasksLayer = reparentSplitTasksForAnimation(apps, t, + "SplitScreenController#onGoingToRecentsLegacy" /* callsite */); + t.apply(); + mTransactionPool.release(t); + + return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()}; } RemoteAnimationTarget[] onStartingSplitLegacy(RemoteAnimationTarget[] apps) { + if (ENABLE_SHELL_TRANSITIONS) return null; + + int openingApps = 0; + for (int i = 0; i < apps.length; ++i) { + if (apps[i].mode == MODE_OPENING) openingApps++; + } + if (openingApps < 2) { + // Not having enough apps to enter split screen + return null; + } + + SurfaceControl.Transaction t = mTransactionPool.acquire(); + if (mStartingSplitTasksLayer != null) { + t.remove(mStartingSplitTasksLayer); + } + mStartingSplitTasksLayer = reparentSplitTasksForAnimation(apps, t, + "SplitScreenController#onStartingSplitLegacy" /* callsite */); + t.apply(); + mTransactionPool.release(t); + try { - return reparentSplitTasksForAnimation(apps, true /* enterSplitScreen */); + return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()}; } finally { for (RemoteAnimationTarget appTarget : apps) { if (appTarget.leash != null) { @@ -531,45 +566,23 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } } - private RemoteAnimationTarget[] reparentSplitTasksForAnimation(RemoteAnimationTarget[] apps, - boolean enterSplitScreen) { - if (ENABLE_SHELL_TRANSITIONS) return null; - - if (enterSplitScreen) { - int openingApps = 0; - for (int i = 0; i < apps.length; ++i) { - if (apps[i].mode == MODE_OPENING) openingApps++; - } - if (openingApps < 2) { - // Not having enough apps to enter split screen - return null; - } - } else if (!isSplitScreenVisible()) { - return null; - } - - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); - if (mSplitTasksContainerLayer != null) { - // Remove the previous layer before recreating - transaction.remove(mSplitTasksContainerLayer); - } + private SurfaceControl reparentSplitTasksForAnimation(RemoteAnimationTarget[] apps, + SurfaceControl.Transaction t, String callsite) { final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) .setContainerLayer() .setName("RecentsAnimationSplitTasks") .setHidden(false) - .setCallsite("SplitScreenController#onGoingtoRecentsLegacy"); + .setCallsite(callsite); mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder); - mSplitTasksContainerLayer = builder.build(); + final SurfaceControl splitTasksLayer = builder.build(); for (int i = 0; i < apps.length; ++i) { final RemoteAnimationTarget appTarget = apps[i]; - transaction.reparent(appTarget.leash, mSplitTasksContainerLayer); - transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left, + t.reparent(appTarget.leash, splitTasksLayer); + t.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left, appTarget.screenSpaceBounds.top); } - transaction.apply(); - mTransactionPool.release(transaction); - return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()}; + return splitTasksLayer; } /** * Sets drag info to be logged when splitscreen is entered. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index db35b48eb898..25793ed0317b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -18,8 +18,6 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED; -import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; @@ -1109,9 +1107,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void addActivityOptions(Bundle opts, StageTaskListener stage) { opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token); - // Put BAL flags to avoid activity start aborted. - opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true); - opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true); } void updateActivityOptions(Bundle opts, @SplitPosition int position) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 83aa539d24d6..e8a2cb160880 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -36,6 +36,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; import com.android.wm.shell.transition.Transitions; @@ -80,6 +81,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption SurfaceControl taskSurface, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { + if (!shouldShowWindowDecor(taskInfo)) return null; final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration( mContext, mDisplayController, @@ -101,9 +103,12 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption @Override public CaptionWindowDecoration adoptWindowDecoration(AutoCloseable windowDecor) { - return (windowDecor instanceof CaptionWindowDecoration) - ? (CaptionWindowDecoration) windowDecor - : null; + if (!(windowDecor instanceof CaptionWindowDecoration)) return null; + final CaptionWindowDecoration captionWindowDecor = (CaptionWindowDecoration) windowDecor; + if (!shouldShowWindowDecor(captionWindowDecor.mTaskInfo)) { + return null; + } + return captionWindowDecor; } @Override @@ -231,4 +236,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption } } } + + private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { + if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true; + return DesktopMode.IS_SUPPORTED + && mDisplayController.getDisplayContext(taskInfo.displayId) + .getResources().getConfiguration().smallestScreenWidthDp >= 600; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java index c234949572bf..d9697d288ab6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java @@ -42,6 +42,7 @@ public interface WindowDecorViewModel<T extends AutoCloseable> { /** * Creates a window decoration for the given task. + * Can be {@code null} for Fullscreen tasks but not Freeform ones. * * @param taskInfo the initial task info of the task * @param taskSurface the surface of the task @@ -49,7 +50,7 @@ public interface WindowDecorViewModel<T extends AutoCloseable> { * @param finishT the finish transaction to restore states after the transition * @return the window decoration object */ - T createWindowDecoration( + @Nullable T createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, SurfaceControl.Transaction startT, @@ -57,11 +58,12 @@ public interface WindowDecorViewModel<T extends AutoCloseable> { /** * Adopts the window decoration if possible. + * May be {@code null} if a window decor is not needed or the given one is incompatible. * * @param windowDecor the potential window decoration to adopt * @return the window decoration if it can be adopted, or {@code null} otherwise. */ - T adoptWindowDecoration(@Nullable AutoCloseable windowDecor); + @Nullable T adoptWindowDecoration(@Nullable AutoCloseable windowDecor); /** * Notifies a task info update on the given task, with the window decoration created previously diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt index 06361f9ca4ab..53dd8b04afeb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt @@ -24,3 +24,7 @@ val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentNameMatcher("", "AppPairSplitDiv val DOCKED_STACK_DIVIDER_COMPONENT = ComponentNameMatcher("", "DockedStackDivider#") val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentNameMatcher("", "StageCoordinatorSplitDivider#") val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#") + +enum class Direction { + UP, DOWN, LEFT, RIGHT +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt index 0f7d8a9510b3..39be89d2592b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt @@ -30,7 +30,7 @@ import org.junit.Test */ abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) { override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = true) { + get() = buildTransition { setup { this.setRotation(testSpec.startRotation) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt index 53450de5221d..c2fd0d78f0d5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt @@ -62,7 +62,7 @@ class ExitPipViaExpandButtonClickTest( * Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = true) { + get() = buildTransition { setup { // launch an app behind the pip one testApp.launchViaIntent(wmHelper) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt index 75018d161267..0d75e02bd467 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt @@ -62,7 +62,7 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit * Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = true) { + get() = buildTransition { setup { // launch an app behind the pip one testApp.launchViaIntent(wmHelper) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index 4d39ec53f18c..513ce95042d0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -56,7 +56,7 @@ import org.junit.runners.Parameterized @Group3 class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = true) { + get() = buildTransition { transitions { pipApp.doubleClickPipWindow(wmHelper) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt index de8c0093bfc5..746ce913c587 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt @@ -16,28 +16,30 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Presubmit +import android.platform.test.annotations.RequiresDevice import android.view.Surface -import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.traces.region.RegionSubject +import com.android.wm.shell.flicker.Direction import org.junit.FixMethodOrder +import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test Pip movement with Launcher shelf height change (decrease). + * Test Pip movement with Launcher shelf height change (increase). * - * To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest` + * To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest` * * Actions: * Launch [pipApp] in pip mode - * Launch [testApp] * Press home + * Launch [testApp] * Check if pip window moves down (visually) * * Notes: @@ -53,26 +55,41 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group3 -open class MovePipDownShelfHeightChangeTest( +class MovePipDownShelfHeightChangeTest( testSpec: FlickerTestParameter ) : MovePipShelfHeightTransition(testSpec) { +// @Before +// fun before() { +// Assume.assumeFalse(isShellTransitionsEnabled) +// } + /** * Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = false) { + get() = buildTransition { teardown { - testApp.launchViaIntent(wmHelper) + tapl.pressHome() testApp.exit(wmHelper) } transitions { - tapl.pressHome() + testApp.launchViaIntent(wmHelper) } } - override fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) { - current.isHigherOrEqual(previous.region) - } + /** + * Checks that the visible region of [pipApp] window always moves down during the animation. + */ + @Presubmit + @Test + fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN) + + /** + * Checks that the visible region of [pipApp] layer always moves down during the animation. + */ + @Presubmit + @Test + fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN) companion object { /** diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt index 9fb941e2a584..19bdca51f8af 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt @@ -19,6 +19,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.traces.region.RegionSubject +import com.android.wm.shell.flicker.Direction import com.android.wm.shell.flicker.helpers.FixedAppHelper import org.junit.Test @@ -31,11 +32,6 @@ abstract class MovePipShelfHeightTransition( protected val testApp = FixedAppHelper(instrumentation) /** - * Checks if the window movement direction is valid - */ - protected abstract fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) - - /** * Checks [pipApp] window remains visible throughout the animation */ @Presubmit @@ -82,31 +78,46 @@ abstract class MovePipShelfHeightTransition( } /** - * Checks that the visible region of [pipApp] always moves in the correct direction + * Checks that the visible region of [pipApp] window always moves in the specified direction * during the animation. */ - @Presubmit - @Test - open fun pipWindowMoves() { + protected fun pipWindowMoves(direction: Direction) { testSpec.assertWm { - val pipWindowList = this.windowStates { pipApp.windowMatchesAnyOf(it) && it.isVisible } - pipWindowList.zipWithNext { previous, current -> - assertRegionMovement(previous.frame, current.frame) + val pipWindowFrameList = this.windowStates { + pipApp.windowMatchesAnyOf(it) && it.isVisible + }.map { it.frame } + when (direction) { + Direction.UP -> assertRegionMovementUp(pipWindowFrameList) + Direction.DOWN -> assertRegionMovementDown(pipWindowFrameList) + else -> error("Unhandled direction") } } } /** - * Checks that the visible region of [pipApp] always moves up during the animation + * Checks that the visible region of [pipApp] layer always moves in the specified direction + * during the animation. */ - @Presubmit - @Test - open fun pipLayerMoves() { + protected fun pipLayerMoves(direction: Direction) { testSpec.assertLayers { - val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } - pipLayerList.zipWithNext { previous, current -> - assertRegionMovement(previous.visibleRegion, current.visibleRegion) + val pipLayerRegionList = this.layers { + pipApp.layerMatchesAnyOf(it) && it.isVisible + }.map { it.visibleRegion } + when (direction) { + Direction.UP -> assertRegionMovementUp(pipLayerRegionList) + Direction.DOWN -> assertRegionMovementDown(pipLayerRegionList) + else -> error("Unhandled direction") } } } + + private fun assertRegionMovementDown(regions: List<RegionSubject>) { + regions.zipWithNext { previous, current -> current.isLowerOrEqual(previous) } + regions.last().isLower(regions.first()) + } + + private fun assertRegionMovementUp(regions: List<RegionSubject>) { + regions.zipWithNext { previous, current -> current.isHigherOrEqual(previous.region) } + regions.last().isHigher(regions.first()) + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt index 3e08bfb247e6..93e7d5ca45ad 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt @@ -16,31 +16,30 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.RequiresDevice +import android.platform.test.annotations.Presubmit import android.view.Surface +import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.traces.region.RegionSubject -import org.junit.Assume -import org.junit.Before +import com.android.wm.shell.flicker.Direction import org.junit.FixMethodOrder +import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test Pip movement with Launcher shelf height change (increase). + * Test Pip movement with Launcher shelf height change (decrease). * - * To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest` + * To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest` * * Actions: * Launch [pipApp] in pip mode - * Press home * Launch [testApp] + * Press home * Check if pip window moves up (visually) * * Notes: @@ -56,31 +55,38 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group3 -class MovePipUpShelfHeightChangeTest( +open class MovePipUpShelfHeightChangeTest( testSpec: FlickerTestParameter ) : MovePipShelfHeightTransition(testSpec) { - @Before - fun before() { - Assume.assumeFalse(isShellTransitionsEnabled) - } - /** * Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = false) { - teardown { - tapl.pressHome() - testApp.exit(wmHelper) + get() = buildTransition() { + setup { + testApp.launchViaIntent(wmHelper) } transitions { - testApp.launchViaIntent(wmHelper) + tapl.pressHome() + } + teardown { + testApp.exit(wmHelper) } } - override fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) { - current.isLowerOrEqual(previous.region) - } + /** + * Checks that the visible region of [pipApp] window always moves up during the animation. + */ + @Presubmit + @Test + fun pipWindowMovesUp() = pipWindowMoves(Direction.UP) + + /** + * Checks that the visible region of [pipApp] layer always moves up during the animation. + */ + @Presubmit + @Test + fun pipLayerMovesUp() = pipLayerMoves(Direction.UP) companion object { /** diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt index 27fc2ed23b83..3d3b53de3dc8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt @@ -56,7 +56,7 @@ open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testS /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = false) { + get() = buildTransition { setup { imeApp.launchViaIntent(wmHelper) setRotation(testSpec.startRotation) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt index fd5ff2933f8c..f13698f1d04e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt @@ -72,7 +72,7 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS } override val transition: FlickerBuilder.() -> Unit - get() = buildTransition(eachRun = false) { + get() = buildTransition { setup { fixedApp.launchViaIntent(wmHelper) setRotation(testSpec.startRotation) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index faf6afc64c69..b67cf772358b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -65,7 +65,6 @@ abstract class PipTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec */ @JvmOverloads protected open fun buildTransition( - eachRun: Boolean, stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"), extraSpec: FlickerBuilder.() -> Unit = {} ): FlickerBuilder.() -> Unit { @@ -73,25 +72,12 @@ abstract class PipTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec setup { setRotation(Surface.ROTATION_0) removeAllTasksButHome() - - if (!eachRun) { - pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras) - } - if (eachRun) { - pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras) - } + pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras) } teardown { setRotation(Surface.ROTATION_0) removeAllTasksButHome() pipApp.exit(wmHelper) - - if (eachRun) { - pipApp.exit(wmHelper) - } - if (!eachRun) { - pipApp.exit(wmHelper) - } } extraSpec(this) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt index 4e8773f22768..2db3009b8d68 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.splitscreen +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit @@ -75,13 +76,13 @@ class DismissSplitScreenByGoHome( @Test fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp) - @IwTest(focusArea = "sysui") - @Presubmit + // TODO(b/245472831): Move back to presubmit after shell transitions landing. + @FlakyTest(bugId = 245472831) @Test fun secondaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp) - @IwTest(focusArea = "sysui") - @Presubmit + // TODO(b/245472831): Move back to presubmit after shell transitions landing. + @FlakyTest(bugId = 245472831) @Test fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible( primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = 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 e174f5ef7917..2d5d2b71b270 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 @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.splitscreen +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit @@ -85,8 +86,9 @@ class SwitchAppByDoubleTapDivider (testSpec: FlickerTestParameter) : SplitScreen fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( primaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true) - @IwTest(focusArea = "sysui") - @Presubmit + // TODO(b/246490534): Move back to presubmit after withAppTransitionIdle is robust enough to + // get the correct end state. + @FlakyTest(bugId = 246490534) @Test fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( secondaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java index ef532e449bd6..577942505b13 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java @@ -43,6 +43,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; import org.junit.Before; import org.junit.Test; @@ -63,6 +64,8 @@ public class DesktopModeControllerTest extends ShellTestCase { private ShellExecutor mTestExecutor; @Mock private Handler mMockHandler; + @Mock + private Transitions mMockTransitions; private DesktopModeController mController; private ShellInit mShellInit; @@ -72,7 +75,7 @@ public class DesktopModeControllerTest extends ShellTestCase { mShellInit = Mockito.spy(new ShellInit(mTestExecutor)); mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer, - mRootDisplayAreaOrganizer, mMockHandler); + mRootDisplayAreaOrganizer, mMockHandler, mMockTransitions); mShellInit.init(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt new file mode 100644 index 000000000000..9b28d11f6a9d --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DesktopModeTaskRepositoryTest : ShellTestCase() { + + private lateinit var repo: DesktopModeTaskRepository + + @Before + fun setUp() { + repo = DesktopModeTaskRepository() + } + + @Test + fun addActiveTask_listenerNotifiedAndTaskIsActive() { + val listener = TestListener() + repo.addListener(listener) + + repo.addActiveTask(1) + assertThat(listener.activeTaskChangedCalls).isEqualTo(1) + assertThat(repo.isActiveTask(1)).isTrue() + } + + @Test + fun addActiveTask_sameTaskDoesNotNotify() { + val listener = TestListener() + repo.addListener(listener) + + repo.addActiveTask(1) + repo.addActiveTask(1) + assertThat(listener.activeTaskChangedCalls).isEqualTo(1) + } + + @Test + fun addActiveTask_multipleTasksAddedNotifiesForEach() { + val listener = TestListener() + repo.addListener(listener) + + repo.addActiveTask(1) + repo.addActiveTask(2) + assertThat(listener.activeTaskChangedCalls).isEqualTo(2) + } + + @Test + fun removeActiveTask_listenerNotifiedAndTaskNotActive() { + val listener = TestListener() + repo.addListener(listener) + + repo.addActiveTask(1) + repo.removeActiveTask(1) + // Notify once for add and once for remove + assertThat(listener.activeTaskChangedCalls).isEqualTo(2) + assertThat(repo.isActiveTask(1)).isFalse() + } + + @Test + fun removeActiveTask_removeNotExistingTaskDoesNotNotify() { + val listener = TestListener() + repo.addListener(listener) + repo.removeActiveTask(99) + assertThat(listener.activeTaskChangedCalls).isEqualTo(0) + } + + @Test + fun isActiveTask_notExistingTaskReturnsFalse() { + assertThat(repo.isActiveTask(99)).isFalse() + } + + class TestListener : DesktopModeTaskRepository.Listener { + var activeTaskChangedCalls = 0 + override fun onActiveTasksChanged() { + activeTaskChangedCalls++ + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index e9a1e2523a86..cadfeb0de312 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -55,6 +55,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopMode; +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.util.GroupedRecentTaskInfo; @@ -82,6 +83,8 @@ public class RecentTasksControllerTest extends ShellTestCase { private TaskStackListenerImpl mTaskStackListener; @Mock private ShellCommandHandler mShellCommandHandler; + @Mock + private DesktopModeTaskRepository mDesktopModeTaskRepository; private ShellTaskOrganizer mShellTaskOrganizer; private RecentTasksController mRecentTasksController; @@ -94,7 +97,8 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class)); mShellInit = spy(new ShellInit(mMainExecutor)); mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit, - mShellCommandHandler, mTaskStackListener, mMainExecutor)); + mShellCommandHandler, mTaskStackListener, Optional.of(mDesktopModeTaskRepository), + mMainExecutor)); mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler, null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController), mMainExecutor); @@ -195,8 +199,8 @@ public class RecentTasksControllerTest extends ShellTestCase { ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); - mRecentTasksController.addActiveFreeformTask(1); - mRecentTasksController.addActiveFreeformTask(3); + when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true); + when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true); ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); diff --git a/location/java/android/location/LocationTime.java b/location/java/android/location/LocationTime.java index e5535d192776..2f03508fbb15 100644 --- a/location/java/android/location/LocationTime.java +++ b/location/java/android/location/LocationTime.java @@ -20,28 +20,32 @@ import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import java.time.Duration; +import java.time.Instant; + /** - * Data class for passing location derived time. + * Data class for passing GNSS-derived time. * @hide */ public final class LocationTime implements Parcelable { - private final long mTime; + private final long mUnixEpochTimeMillis; private final long mElapsedRealtimeNanos; - public LocationTime(long time, long elapsedRealtimeNanos) { - mTime = time; + public LocationTime(long unixEpochTimeMillis, long elapsedRealtimeNanos) { + mUnixEpochTimeMillis = unixEpochTimeMillis; mElapsedRealtimeNanos = elapsedRealtimeNanos; } /** - * The current time, according to the Gnss location provider. */ - public long getTime() { - return mTime; + * The Unix epoch time in millis, according to the Gnss location provider. + */ + public long getUnixEpochTimeMillis() { + return mUnixEpochTimeMillis; } /** - * The elapsed nanos since boot {@link #getTime} was computed at. + * The elapsed nanos since boot when {@link #getUnixEpochTimeMillis} was the current time. */ public long getElapsedRealtimeNanos() { return mElapsedRealtimeNanos; @@ -49,7 +53,7 @@ public final class LocationTime implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeLong(mTime); + out.writeLong(mUnixEpochTimeMillis); out.writeLong(mElapsedRealtimeNanos); } @@ -58,8 +62,18 @@ public final class LocationTime implements Parcelable { return 0; } + @Override + public String toString() { + return "LocationTime{" + + "mUnixEpochTimeMillis=" + Instant.ofEpochMilli(mUnixEpochTimeMillis) + + "(" + mUnixEpochTimeMillis + ")" + + ", mElapsedRealtimeNanos=" + Duration.ofNanos(mElapsedRealtimeNanos) + + "(" + mElapsedRealtimeNanos + ")" + + '}'; + } + public static final @NonNull Parcelable.Creator<LocationTime> CREATOR = - new Parcelable.Creator<LocationTime>() { + new Parcelable.Creator<>() { public LocationTime createFromParcel(Parcel in) { long time = in.readLong(); long elapsedRealtimeNanos = in.readLong(); diff --git a/packages/CredentialManager/OWNERS b/packages/CredentialManager/OWNERS new file mode 100644 index 000000000000..f3b43c171025 --- /dev/null +++ b/packages/CredentialManager/OWNERS @@ -0,0 +1 @@ +include /core/java/android/credentials/OWNERS diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle index 5dc2aa0d61cb..f8667edb7e8c 100644 --- a/packages/SettingsLib/Spa/build.gradle +++ b/packages/SettingsLib/Spa/build.gradle @@ -16,7 +16,7 @@ buildscript { ext { - spa_min_sdk = 31 + spa_min_sdk = 21 jetpack_compose_version = '1.2.0-alpha04' jetpack_compose_material3_version = '1.0.0-alpha06' } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt index 787d0961fb21..783fef7e91cf 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt @@ -19,6 +19,7 @@ package com.android.settingslib.spa.gallery import com.android.settingslib.spa.framework.common.SettingsEntryRepository import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository +import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider import com.android.settingslib.spa.gallery.home.HomePageProvider import com.android.settingslib.spa.gallery.page.ArgumentPageProvider import com.android.settingslib.spa.gallery.page.FooterPageProvider @@ -30,6 +31,7 @@ import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider import com.android.settingslib.spa.gallery.preference.PreferencePageProvider import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider +import com.android.settingslib.spa.gallery.ui.CategoryPageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider object SpaEnvironment { @@ -49,6 +51,8 @@ object SpaEnvironment { SettingsPagerPageProvider, FooterPageProvider, IllustrationPageProvider, + CategoryPageProvider, + ActionButtonPageProvider, ), rootPages = listOf( SettingsPage.create(HomePageProvider.name) diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt new file mode 100644 index 000000000000..96e2498e39bf --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.gallery.button + +import android.os.Bundle +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.Launch +import androidx.compose.material.icons.outlined.WarningAmber +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +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.compose.navigator +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.button.ActionButton +import com.android.settingslib.spa.widget.button.ActionButtons +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 ActionButton" + +object ActionButtonPageProvider : SettingsPageProvider { + override val name = "ActionButton" + + @Composable + override fun Page(arguments: Bundle?) { + ActionButtonPage() + } + + fun buildInjectEntry(): SettingsEntryBuilder { + return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name)) + .setIsAllowSearch(true) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + } +} + +@Composable +fun ActionButtonPage() { + RegularScaffold(title = TITLE) { + val actionButtons = listOf( + ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {}, + ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {}, + ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {}, + ) + ActionButtons(actionButtons) + } +} + +@Preview(showBackground = true) +@Composable +private fun ActionButtonPagePreview() { + SettingsTheme { + ActionButtonPage() + } +} 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 d8ef937b50c2..a16ee6c85409 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 @@ -25,6 +25,7 @@ import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.gallery.R +import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider import com.android.settingslib.spa.gallery.page.ArgumentPageModel import com.android.settingslib.spa.gallery.page.ArgumentPageProvider import com.android.settingslib.spa.gallery.page.FooterPageProvider @@ -32,6 +33,7 @@ import com.android.settingslib.spa.gallery.page.IllustrationPageProvider import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider import com.android.settingslib.spa.gallery.page.SliderPageProvider import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider +import com.android.settingslib.spa.gallery.ui.CategoryPageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider import com.android.settingslib.spa.widget.scaffold.HomeScaffold @@ -48,6 +50,8 @@ object HomePageProvider : SettingsPageProvider { SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), FooterPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ) } 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 new file mode 100644 index 000000000000..9a525bf5426d --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.gallery.ui + +import android.os.Bundle +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.tooling.preview.Preview +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.compose.navigator +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold +import com.android.settingslib.spa.widget.ui.Category +import com.android.settingslib.spa.widget.ui.CategoryTitle + +private const val TITLE = "Sample Category" + +object CategoryPageProvider : SettingsPageProvider { + override val name = "Spinner" + + fun buildInjectEntry(): SettingsEntryBuilder { + return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name)) + .setIsAllowSearch(true) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + } + + @Composable + override fun Page(arguments: Bundle?) { + CategoryPage() + } +} + +@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") { + 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") + } + }) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun SpinnerPagePreview() { + SettingsTheme { + SpinnerPageProvider.Page(null) + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt index 4626f0bbed49..3fa8c658e198 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt @@ -16,11 +16,15 @@ package com.android.settingslib.spa.framework.theme +import android.os.Build import androidx.compose.material3.ColorScheme +import androidx.compose.material3.darkColorScheme import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @Composable @@ -28,8 +32,15 @@ internal fun materialColorScheme(isDarkTheme: Boolean): ColorScheme { val context = LocalContext.current return remember(isDarkTheme) { when { - isDarkTheme -> dynamicDarkColorScheme(context) - else -> dynamicLightColorScheme(context) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + if (isDarkTheme) dynamicDarkColorScheme(context) + else dynamicLightColorScheme(context) + } + isDarkTheme -> darkColorScheme() + else -> lightColorScheme() } } } + +val ColorScheme.divider: Color + get() = onSurface.copy(SettingsOpacity.Divider) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt index bc316f71cf23..fa17e08e9885 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt @@ -17,6 +17,7 @@ package com.android.settingslib.spa.framework.theme import android.content.Context +import android.os.Build import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf @@ -44,8 +45,12 @@ internal fun settingsColorScheme(isDarkTheme: Boolean): SettingsColorScheme { val context = LocalContext.current return remember(isDarkTheme) { when { - isDarkTheme -> dynamicDarkColorScheme(context) - else -> dynamicLightColorScheme(context) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + if (isDarkTheme) dynamicDarkColorScheme(context) + else dynamicLightColorScheme(context) + } + isDarkTheme -> darkColorScheme() + else -> lightColorScheme() } } } @@ -101,3 +106,37 @@ private fun dynamicDarkColorScheme(context: Context): SettingsColorScheme { onSpinnerItemContainer = tonalPalette.neutralVariant30, ) } + +private fun darkColorScheme(): SettingsColorScheme { + val tonalPalette = tonalPalette() + return SettingsColorScheme( + background = tonalPalette.neutral10, + categoryTitle = tonalPalette.primary90, + surface = tonalPalette.neutral20, + surfaceHeader = tonalPalette.neutral30, + secondaryText = tonalPalette.neutralVariant80, + primaryContainer = tonalPalette.secondary90, + onPrimaryContainer = tonalPalette.neutral10, + spinnerHeaderContainer = tonalPalette.primary90, + onSpinnerHeaderContainer = tonalPalette.neutral10, + spinnerItemContainer = tonalPalette.secondary90, + onSpinnerItemContainer = tonalPalette.neutralVariant30, + ) +} + +private fun lightColorScheme(): SettingsColorScheme { + val tonalPalette = tonalPalette() + return SettingsColorScheme( + background = tonalPalette.neutral95, + categoryTitle = tonalPalette.primary40, + surface = tonalPalette.neutral99, + surfaceHeader = tonalPalette.neutral90, + secondaryText = tonalPalette.neutralVariant30, + primaryContainer = tonalPalette.primary90, + onPrimaryContainer = tonalPalette.neutral10, + spinnerHeaderContainer = tonalPalette.primary90, + onSpinnerHeaderContainer = tonalPalette.neutral10, + spinnerItemContainer = tonalPalette.secondary90, + onSpinnerItemContainer = tonalPalette.neutralVariant30, + ) +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt index f81f5e734fb4..c205aae84fd0 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt @@ -118,6 +118,84 @@ internal class SettingsTonalPalette( val tertiary0: Color, ) +/** Static colors in Material. */ +internal fun tonalPalette() = SettingsTonalPalette( + // The neutral static tonal range. + neutral100 = Color(red = 255, green = 255, blue = 255), + neutral99 = Color(red = 255, green = 251, blue = 254), + neutral95 = Color(red = 244, green = 239, blue = 244), + neutral90 = Color(red = 230, green = 225, blue = 229), + neutral80 = Color(red = 201, green = 197, blue = 202), + neutral70 = Color(red = 174, green = 170, blue = 174), + neutral60 = Color(red = 147, green = 144, blue = 148), + neutral50 = Color(red = 120, green = 117, blue = 121), + neutral40 = Color(red = 96, green = 93, blue = 98), + neutral30 = Color(red = 72, green = 70, blue = 73), + neutral20 = Color(red = 49, green = 48, blue = 51), + neutral10 = Color(red = 28, green = 27, blue = 31), + neutral0 = Color(red = 0, green = 0, blue = 0), + + // The neutral variant static tonal range, sometimes called "neutral 2". + neutralVariant100 = Color(red = 255, green = 255, blue = 255), + neutralVariant99 = Color(red = 255, green = 251, blue = 254), + neutralVariant95 = Color(red = 245, green = 238, blue = 250), + neutralVariant90 = Color(red = 231, green = 224, blue = 236), + neutralVariant80 = Color(red = 202, green = 196, blue = 208), + neutralVariant70 = Color(red = 174, green = 169, blue = 180), + neutralVariant60 = Color(red = 147, green = 143, blue = 153), + neutralVariant50 = Color(red = 121, green = 116, blue = 126), + neutralVariant40 = Color(red = 96, green = 93, blue = 102), + neutralVariant30 = Color(red = 73, green = 69, blue = 79), + neutralVariant20 = Color(red = 50, green = 47, blue = 55), + neutralVariant10 = Color(red = 29, green = 26, blue = 34), + neutralVariant0 = Color(red = 0, green = 0, blue = 0), + + // The primary static tonal range. + primary100 = Color(red = 255, green = 255, blue = 255), + primary99 = Color(red = 255, green = 251, blue = 254), + primary95 = Color(red = 246, green = 237, blue = 255), + primary90 = Color(red = 234, green = 221, blue = 255), + primary80 = Color(red = 208, green = 188, blue = 255), + primary70 = Color(red = 182, green = 157, blue = 248), + primary60 = Color(red = 154, green = 130, blue = 219), + primary50 = Color(red = 127, green = 103, blue = 190), + primary40 = Color(red = 103, green = 80, blue = 164), + primary30 = Color(red = 79, green = 55, blue = 139), + primary20 = Color(red = 56, green = 30, blue = 114), + primary10 = Color(red = 33, green = 0, blue = 93), + primary0 = Color(red = 33, green = 0, blue = 93), + + // The secondary static tonal range. + secondary100 = Color(red = 255, green = 255, blue = 255), + secondary99 = Color(red = 255, green = 251, blue = 254), + secondary95 = Color(red = 246, green = 237, blue = 255), + secondary90 = Color(red = 232, green = 222, blue = 248), + secondary80 = Color(red = 204, green = 194, blue = 220), + secondary70 = Color(red = 176, green = 167, blue = 192), + secondary60 = Color(red = 149, green = 141, blue = 165), + secondary50 = Color(red = 122, green = 114, blue = 137), + secondary40 = Color(red = 98, green = 91, blue = 113), + secondary30 = Color(red = 74, green = 68, blue = 88), + secondary20 = Color(red = 51, green = 45, blue = 65), + secondary10 = Color(red = 29, green = 25, blue = 43), + secondary0 = Color(red = 0, green = 0, blue = 0), + + // The tertiary static tonal range. + tertiary100 = Color(red = 255, green = 255, blue = 255), + tertiary99 = Color(red = 255, green = 251, blue = 250), + tertiary95 = Color(red = 255, green = 236, blue = 241), + tertiary90 = Color(red = 255, green = 216, blue = 228), + tertiary80 = Color(red = 239, green = 184, blue = 200), + tertiary70 = Color(red = 210, green = 157, blue = 172), + tertiary60 = Color(red = 181, green = 131, blue = 146), + tertiary50 = Color(red = 152, green = 105, blue = 119), + tertiary40 = Color(red = 125, green = 82, blue = 96), + tertiary30 = Color(red = 99, green = 59, blue = 72), + tertiary20 = Color(red = 73, green = 37, blue = 50), + tertiary10 = Color(red = 49, green = 17, blue = 29), + tertiary0 = Color(red = 0, green = 0, blue = 0), +) + /** Dynamic colors in Material. */ internal fun dynamicTonalPalette(context: Context) = SettingsTonalPalette( // The neutral tonal range from the generated dynamic color palette. diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt new file mode 100644 index 000000000000..3555ec746789 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt @@ -0,0 +1,127 @@ +/* + * 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.widget.button + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.Launch +import androidx.compose.material.icons.outlined.WarningAmber +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsShape +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.theme.divider + +data class ActionButton( + val text: String, + val imageVector: ImageVector, + val onClick: () -> Unit, +) + +@Composable +fun ActionButtons(actionButtons: List<ActionButton>) { + Row( + Modifier + .padding(SettingsDimension.itemPaddingVertical) + .clip(SettingsShape.CornerLarge) + .height(IntrinsicSize.Min) + ) { + for ((index, actionButton) in actionButtons.withIndex()) { + if (index > 0) ButtonDivider() + ActionButton(actionButton) + } + } +} + +@Composable +private fun RowScope.ActionButton(actionButton: ActionButton) { + FilledTonalButton( + onClick = actionButton.onClick, + modifier = Modifier + .weight(1f) + .fillMaxHeight(), + shape = RectangleShape, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = MaterialTheme.colorScheme.surface, + contentColor = SettingsTheme.colorScheme.categoryTitle, + ), + contentPadding = PaddingValues(horizontal = 4.dp, vertical = 20.dp), + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + imageVector = actionButton.imageVector, + contentDescription = null, + modifier = Modifier.size(SettingsDimension.itemIconSize), + ) + Spacer(Modifier.height(4.dp)) + Text( + text = actionButton.text, + style = MaterialTheme.typography.labelLarge, + ) + } + } +} + +@Composable +private fun ButtonDivider() { + Box( + Modifier + .width(1.dp) + .background(color = MaterialTheme.colorScheme.divider) + ) +} + +@Preview +@Composable +private fun ActionButtonsPreview() { + SettingsTheme { + ActionButtons( + listOf( + ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {}, + ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {}, + ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {}, + ) + ) + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt index 77ea3c5448e4..566361029ff6 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt @@ -30,7 +30,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.theme.SettingsDimension -import com.android.settingslib.spa.framework.theme.SettingsOpacity +import com.android.settingslib.spa.framework.theme.divider @Composable internal fun TwoTargetPreference( @@ -67,6 +67,6 @@ private fun PreferenceDivider() { Modifier .padding(horizontal = SettingsDimension.itemPaddingEnd) .size(width = 1.dp, height = SettingsDimension.itemDividerHeight) - .background(color = MaterialTheme.colorScheme.onSurface.copy(SettingsOpacity.Divider)) + .background(color = MaterialTheme.colorScheme.divider) ) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt new file mode 100644 index 000000000000..6aac5bf3839a --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsTheme + +/** + * A category title that is placed before a group of similar items. + */ +@Composable +fun CategoryTitle(title: String) { + Text( + text = title, + modifier = Modifier.padding( + start = SettingsDimension.itemPaddingStart, + top = 20.dp, + end = SettingsDimension.itemPaddingEnd, + bottom = 8.dp, + ), + color = SettingsTheme.colorScheme.categoryTitle, + style = MaterialTheme.typography.labelMedium, + ) +} + +/** + * A container that is used to group similar items. A [Category] displays a [CategoryTitle] and + * visually separates groups of items. + */ +@Composable +fun Category(title: String, content: @Composable ColumnScope.() -> Unit) { + Column { + var displayTitle by remember { mutableStateOf(false) } + if (displayTitle) CategoryTitle(title = title) + Column( + modifier = Modifier.onGloballyPositioned { coordinates -> + displayTitle = coordinates.size.height > 0 + }, + content = content, + ) + } +} + +@Preview +@Composable +private fun CategoryPreview() { + SettingsTheme { + CategoryTitle("Appearance") + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt new file mode 100644 index 000000000000..8101a94a9461 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.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.settingslib.spa.widget.button + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Launch +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +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 + +@RunWith(AndroidJUnit4::class) +class ActionButtonsTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun button_displayed() { + composeTestRule.setContent { + ActionButtons( + listOf( + ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {}, + ) + ) + } + + composeTestRule.onNodeWithText("Open").assertIsDisplayed() + } + + @Test + fun button_clickable() { + var clicked by mutableStateOf(false) + composeTestRule.setContent { + ActionButtons( + listOf( + ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) { + clicked = true + }, + ) + ) + } + + composeTestRule.onNodeWithText("Open").performClick() + + assertThat(clicked).isTrue() + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt new file mode 100644 index 000000000000..16e09ee854d8 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt @@ -0,0 +1,70 @@ +/* + * 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.widget.ui + +import androidx.compose.runtime.remember +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class CategoryTest { + + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun categoryTitle() { + composeTestRule.setContent { + CategoryTitle(title = "CategoryTitle") + } + + composeTestRule.onNodeWithText("CategoryTitle").assertIsDisplayed() + } + + @Test + fun category_hasContent_titleDisplayed() { + composeTestRule.setContent { + Category(title = "CategoryTitle") { + Preference(remember { + object : PreferenceModel { + override val title = "Some Preference" + override val summary = stateOf("Some summary") + } + }) + } + } + + composeTestRule.onNodeWithText("CategoryTitle").assertIsDisplayed() + } + + @Test + fun category_noContent_titleNotDisplayed() { + composeTestRule.setContent { + Category(title = "CategoryTitle") {} + } + + composeTestRule.onNodeWithText("CategoryTitle").assertDoesNotExist() + } +} diff --git a/packages/SettingsLib/SpaPrivileged/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values/strings.xml index b2302a58229c..25dbe007bac7 100644 --- a/packages/SettingsLib/SpaPrivileged/res/values/strings.xml +++ b/packages/SettingsLib/SpaPrivileged/res/values/strings.xml @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -<resources> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- [CHAR LIMIT=25] Text shown when there are no applications to display. --> <string name="no_applications">No apps.</string> <!-- [CHAR LIMIT=NONE] Menu for manage apps to control whether system processes are shown --> @@ -25,4 +25,6 @@ <string name="app_permission_summary_allowed">Allowed</string> <!-- Preference summary text for an app when it is disallowed for a permission. [CHAR LIMIT=45] --> <string name="app_permission_summary_not_allowed">Not allowed</string> + <!-- Manage applications, version string displayed in app snippet --> + <string name="version_text">version <xliff:g id="version_num">%1$s</xliff:g></string> </resources> diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt index 6e1afd94fe29..99a08abad8b7 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt @@ -22,4 +22,7 @@ import android.os.UserHandle val ApplicationInfo.userId: Int get() = UserHandle.getUserId(uid) +val ApplicationInfo.userHandle: UserHandle + get() = UserHandle.getUserHandleForUid(uid) + fun ApplicationInfo.toRoute() = "$packageName/$userId" diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt index f51d2dbfdeef..c89ffe560eb7 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt @@ -17,6 +17,8 @@ package com.android.settingslib.spaprivileged.template.app import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import android.text.BidiFormatter import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -25,49 +27,60 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.material3.Divider import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.compose.rememberDrawablePainter import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.widget.ui.SettingsBody import com.android.settingslib.spa.widget.ui.SettingsTitle -import com.android.settingslib.spaprivileged.model.app.PackageManagers +import com.android.settingslib.spaprivileged.R import com.android.settingslib.spaprivileged.model.app.rememberAppRepository -@Composable -fun AppInfo(packageName: String, userId: Int) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = SettingsDimension.itemPaddingStart, - vertical = SettingsDimension.itemPaddingVertical, - ), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - val packageInfo = - remember { PackageManagers.getPackageInfoAsUser(packageName, userId) } ?: return - Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) { - AppIcon(app = packageInfo.applicationInfo, size = SettingsDimension.appIconInfoSize) +class AppInfoProvider(private val packageInfo: PackageInfo) { + @Composable + fun AppInfo(displayVersion: Boolean = false) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = SettingsDimension.itemPaddingStart, + vertical = SettingsDimension.itemPaddingVertical, + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) { + AppIcon(app = packageInfo.applicationInfo, size = SettingsDimension.appIconInfoSize) + } + AppLabel(packageInfo.applicationInfo) + if (displayVersion) AppVersion() } - AppLabel(packageInfo.applicationInfo) - AppVersion(packageInfo.versionName) } -} -@Composable -private fun AppVersion(versionName: String?) { - if (versionName == null) return - Spacer(modifier = Modifier.height(4.dp)) - SettingsBody(versionName) + @Composable + private fun AppVersion() { + if (packageInfo.versionName == null) return + Spacer(modifier = Modifier.height(4.dp)) + SettingsBody(packageInfo.versionName) + } + + @Composable + fun FooterAppVersion() { + if (packageInfo.versionName == null) return + Divider() + Box(modifier = Modifier.padding(SettingsDimension.itemPadding)) { + val versionName = BidiFormatter.getInstance().unicodeWrap(packageInfo.versionName) + SettingsBody(stringResource(R.string.version_text, versionName)) + } + } } @Composable -fun AppIcon(app: ApplicationInfo, size: Dp) { +internal fun AppIcon(app: ApplicationInfo, size: Dp) { val appRepository = rememberAppRepository() Image( painter = rememberDrawablePainter(appRepository.produceIcon(app).value), @@ -77,7 +90,7 @@ fun AppIcon(app: ApplicationInfo, size: Dp) { } @Composable -fun AppLabel(app: ApplicationInfo) { +internal fun AppLabel(app: ApplicationInfo) { val appRepository = rememberAppRepository() SettingsTitle(appRepository.produceLabel(app)) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt index 9b45318ffd82..4f88398b9550 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt @@ -17,8 +17,10 @@ package com.android.settingslib.spaprivileged.template.app import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import com.android.settingslib.spa.widget.scaffold.RegularScaffold import com.android.settingslib.spa.widget.ui.Footer +import com.android.settingslib.spaprivileged.model.app.PackageManagers @Composable fun AppInfoPage( @@ -29,7 +31,12 @@ fun AppInfoPage( content: @Composable () -> Unit, ) { RegularScaffold(title = title) { - AppInfo(packageName, userId) + val appInfoProvider = remember { + val packageInfo = PackageManagers.getPackageInfoAsUser(packageName, userId) + ?: return@RegularScaffold + AppInfoProvider(packageInfo) + } + appInfoProvider.AppInfo(displayVersion = true) content() diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt index ae4f8bd1c597..80b2364ce68d 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt @@ -34,6 +34,8 @@ 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.compose.navigator +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.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.model.app.PackageManagers @@ -80,10 +82,31 @@ internal class TogglePermissionAppInfoPageProvider( companion object { @Composable - internal fun navigator(permissionType: String, app: ApplicationInfo) = + fun navigator(permissionType: String, app: ApplicationInfo) = navigator(route = "$PAGE_NAME/$permissionType/${app.toRoute()}") - internal fun buildPageData(permissionType: String): SettingsPage { + @Composable + fun <T : AppRecord> EntryItem( + permissionType: String, + app: ApplicationInfo, + listModel: TogglePermissionAppListModel<T>, + ) { + val context = LocalContext.current + val internalListModel = remember { + TogglePermissionInternalAppListModel(context, listModel) + } + val record = remember { listModel.transformItem(app) } + if (!remember { listModel.isChangeable(record) }) return + Preference( + object : PreferenceModel { + override val title = stringResource(listModel.pageTitleResId) + override val summary = internalListModel.getSummary(record) + override val onClick = navigator(permissionType, app) + } + ) + } + + fun buildPageData(permissionType: String): SettingsPage { return SettingsPage.create( PAGE_NAME, PAGE_PARAMETER, bundleOf(PERMISSION to permissionType)) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt index 74a50de1da92..5d49d24ecf8e 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt @@ -20,13 +20,10 @@ import android.content.Context import android.content.pm.ApplicationInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import androidx.compose.ui.res.stringResource import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.compose.rememberContext import com.android.settingslib.spa.framework.util.asyncMapItem -import com.android.settingslib.spa.widget.preference.Preference -import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spaprivileged.model.app.AppRecord import kotlinx.coroutines.flow.Flow @@ -85,28 +82,22 @@ interface TogglePermissionAppListProvider { fun createModel(context: Context): TogglePermissionAppListModel<out AppRecord> - fun buildInjectEntry(): SettingsEntryBuilder { - return TogglePermissionAppListPageProvider.buildInjectEntry(permissionType) - } - - @Composable - fun EntryItem() { - val listModel = rememberContext(::createModel) - Preference( - object : PreferenceModel { - override val title = stringResource(listModel.pageTitleResId) - override val onClick = TogglePermissionAppListPageProvider.navigator(permissionType) - } - ) - } + fun buildAppListInjectEntry(): SettingsEntryBuilder = + TogglePermissionAppListPageProvider.buildInjectEntry(permissionType) { createModel(it) } /** * Gets the route to the toggle permission App List page. * * Expose route to enable enter from non-SPA pages. */ - fun getRoute(): String = + fun getAppListRoute(): String = TogglePermissionAppListPageProvider.getRoute(permissionType) + + @Composable + fun InfoPageEntryItem(app: ApplicationInfo) { + val listModel = rememberContext(::createModel) + TogglePermissionAppInfoPageProvider.EntryItem(permissionType, app, listModel) + } } class TogglePermissionAppListTemplate( diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt index 475e93053e5a..bd60bd3699c1 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt @@ -34,7 +34,10 @@ 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.compose.navigator +import com.android.settingslib.spa.framework.compose.rememberContext import com.android.settingslib.spa.framework.util.getStringArg +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spaprivileged.R import com.android.settingslib.spaprivileged.model.app.AppListModel import com.android.settingslib.spaprivileged.model.app.AppRecord @@ -107,20 +110,29 @@ internal class TogglePermissionAppListPageProvider( * * Expose route to enable enter from non-SPA pages. */ - internal fun getRoute(permissionType: String) = "$PAGE_NAME/$permissionType" + fun getRoute(permissionType: String) = "$PAGE_NAME/$permissionType" - @Composable - internal fun navigator(permissionType: String) = navigator(route = getRoute(permissionType)) - - internal fun buildInjectEntry(permissionType: String): SettingsEntryBuilder { + fun buildInjectEntry( + permissionType: String, + listModelSupplier: (Context) -> TogglePermissionAppListModel<out AppRecord>, + ): SettingsEntryBuilder { val appListPage = SettingsPage.create( PAGE_NAME, PAGE_PARAMETER, bundleOf(PERMISSION to permissionType)) return SettingsEntryBuilder.createInject(owner = appListPage).setIsAllowSearch(false) + .setUiLayoutFn { + val listModel = rememberContext(listModelSupplier) + Preference( + object : PreferenceModel { + override val title = stringResource(listModel.pageTitleResId) + override val onClick = navigator(route = getRoute(permissionType)) + } + ) + } } } } -private class TogglePermissionInternalAppListModel<T : AppRecord>( +internal class TogglePermissionInternalAppListModel<T : AppRecord>( private val context: Context, private val listModel: TogglePermissionAppListModel<T>, ) : AppListModel<T> { @@ -132,6 +144,11 @@ private class TogglePermissionInternalAppListModel<T : AppRecord>( @Composable override fun getSummary(option: Int, record: T): State<String> { + return getSummary(record) + } + + @Composable + fun getSummary(record: T): State<String> { val restrictionsProvider = remember { val restrictions = Restrictions( userId = record.app.userId, diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp index 45746d9848df..cfff519705f2 100644 --- a/packages/SettingsLib/search/Android.bp +++ b/packages/SettingsLib/search/Android.bp @@ -19,11 +19,11 @@ java_plugin { name: "SettingsLib-annotation-processor", processor_class: "com.android.settingslib.search.IndexableProcessor", static_libs: [ - "javapoet-prebuilt-jar", + "javapoet", ], srcs: [ "processor-src/**/*.java", - "src/com/android/settingslib/search/SearchIndexable.java" + "src/com/android/settingslib/search/SearchIndexable.java", ], java_resource_dirs: ["resources"], } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java index 3152e65d5a36..fa056e2b77bd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java @@ -16,6 +16,22 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED; +import static android.bluetooth.BluetoothAdapter.STATE_CONNECTING; +import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTED; +import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTING; +import static android.bluetooth.BluetoothAdapter.STATE_OFF; +import static android.bluetooth.BluetoothAdapter.STATE_ON; +import static android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF; +import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON; + +import android.annotation.IntDef; +import android.annotation.Nullable; + +import androidx.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * BluetoothCallback provides a callback interface for the settings @@ -33,7 +49,7 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothAdapter#STATE_ON}, * {@link android.bluetooth.BluetoothAdapter#STATE_TURNING_OFF}. */ - default void onBluetoothStateChanged(int bluetoothState) {} + default void onBluetoothStateChanged(@AdapterState int bluetoothState) {} /** * It will be called when the local Bluetooth adapter has started @@ -54,14 +70,14 @@ public interface BluetoothCallback { * * @param cachedDevice the Bluetooth device. */ - default void onDeviceAdded(CachedBluetoothDevice cachedDevice) {} + default void onDeviceAdded(@NonNull CachedBluetoothDevice cachedDevice) {} /** * It will be called when requiring to remove a remote device from CachedBluetoothDevice list * * @param cachedDevice the Bluetooth device. */ - default void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {} + default void onDeviceDeleted(@NonNull CachedBluetoothDevice cachedDevice) {} /** * It will be called when bond state of a remote device is changed. @@ -73,7 +89,8 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothDevice#BOND_BONDING}, * {@link android.bluetooth.BluetoothDevice#BOND_BONDED}. */ - default void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {} + default void onDeviceBondStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int bondState) {} /** * It will be called in following situations: @@ -89,7 +106,9 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED}, * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTING}. */ - default void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {} + default void onConnectionStateChanged( + @Nullable CachedBluetoothDevice cachedDevice, + @ConnectionState int state) {} /** * It will be called when device been set as active for {@code bluetoothProfile} @@ -101,7 +120,8 @@ public interface BluetoothCallback { * @param activeDevice the active Bluetooth device. * @param bluetoothProfile the profile of active Bluetooth device. */ - default void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {} + default void onActiveDeviceChanged( + @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {} /** * It will be called in following situations: @@ -124,8 +144,10 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothProfile#STATE_DISCONNECTING}. * @param bluetoothProfile the BluetoothProfile id. */ - default void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, - int state, int bluetoothProfile) { + default void onProfileConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, + @ConnectionState int state, + int bluetoothProfile) { } /** @@ -138,6 +160,24 @@ public interface BluetoothCallback { * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTED}, * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED} */ - default void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { - } + default void onAclConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int state) {} + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "STATE_" }, value = { + STATE_DISCONNECTED, + STATE_CONNECTING, + STATE_CONNECTED, + STATE_DISCONNECTING, + }) + @interface ConnectionState {} + + @IntDef(prefix = { "STATE_" }, value = { + STATE_OFF, + STATE_TURNING_ON, + STATE_ON, + STATE_TURNING_OFF, + }) + @Retention(RetentionPolicy.SOURCE) + @interface AdapterState {} } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 51812f043908..a9f4e9c74103 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -32,6 +32,7 @@ import android.os.UserHandle; import android.telephony.TelephonyManager; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -193,19 +194,19 @@ public class BluetoothEventManager { return deviceAdded; } - void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) { + void dispatchDeviceAdded(@NonNull CachedBluetoothDevice cachedDevice) { for (BluetoothCallback callback : mCallbacks) { callback.onDeviceAdded(cachedDevice); } } - void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) { + void dispatchDeviceRemoved(@NonNull CachedBluetoothDevice cachedDevice) { for (BluetoothCallback callback : mCallbacks) { callback.onDeviceDeleted(cachedDevice); } } - void dispatchProfileConnectionStateChanged(CachedBluetoothDevice device, int state, + void dispatchProfileConnectionStateChanged(@NonNull CachedBluetoothDevice device, int state, int bluetoothProfile) { for (BluetoothCallback callback : mCallbacks) { callback.onProfileConnectionStateChanged(device, state, bluetoothProfile); @@ -228,7 +229,8 @@ public class BluetoothEventManager { } @VisibleForTesting - void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice, + void dispatchActiveDeviceChanged( + @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { for (CachedBluetoothDevice cachedDevice : mDeviceManager.getCachedDevicesCopy()) { boolean isActive = Objects.equals(cachedDevice, activeDevice); @@ -239,7 +241,7 @@ public class BluetoothEventManager { } } - private void dispatchAclStateChanged(CachedBluetoothDevice activeDevice, int state) { + private void dispatchAclStateChanged(@NonNull CachedBluetoothDevice activeDevice, int state) { for (BluetoothCallback callback : mCallbacks) { callback.onAclConnectionStateChanged(activeDevice, state); } @@ -456,6 +458,7 @@ public class BluetoothEventManager { Log.w(TAG, "ActiveDeviceChangedHandler: action is null"); return; } + @Nullable CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device); int bluetoothProfile = 0; if (Objects.equals(action, BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) { diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java index d84e57a38ee4..3e33da5a2ba7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java @@ -49,6 +49,10 @@ public class EventLogWriter implements LogWriter { } @Override + public void clicked(int sourceCategory, String key) { + } + + @Override public void action(Context context, int category, Pair<Integer, Object>... taggedData) { final LogMaker logMaker = new LogMaker(category) .setType(MetricsProto.MetricsEvent.TYPE_ACTION); diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java index d4ef3d7b24a2..cceca13ec3e5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java @@ -34,6 +34,11 @@ public interface LogWriter { void hidden(Context context, int category, int visibleTime); /** + * Logs a click event when user click item. + */ + void clicked(int category, String key); + + /** * Logs an user action. */ void action(Context context, int category, Pair<Integer, Object>... taggedData); diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java index 7390b6aaaaed..915421a8f7a0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java @@ -96,6 +96,18 @@ public class MetricsFeatureProvider { } /** + * Logs an event when user click item. + * + * @param category the target page id + * @param key the key id that user clicked + */ + public void clicked(int category, String key) { + for (LogWriter writer : mLoggerWriters) { + writer.clicked(category, key); + } + } + + /** * Logs a simple action without page id or attribution * * @param category the target page @@ -138,7 +150,7 @@ public class MetricsFeatureProvider { } public int getMetricsCategory(Object object) { - if (object == null || !(object instanceof Instrumentable)) { + if (!(object instanceof Instrumentable)) { return MetricsEvent.VIEW_UNKNOWN; } return ((Instrumentable) object).getMetricsCategory(); @@ -198,11 +210,7 @@ public class MetricsFeatureProvider { // Not loggable return false; } - action(sourceMetricsCategory, - MetricsEvent.ACTION_SETTINGS_TILE_CLICK, - SettingsEnums.PAGE_UNKNOWN, - logKey, - 0); + clicked(sourceMetricsCategory, logKey); return true; } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java index 0e2a3cbbbd75..3352d86b2dcc 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java @@ -80,12 +80,7 @@ public class MetricsFeatureProviderTest { MetricsEvent.SETTINGS_GESTURES); assertThat(loggable).isTrue(); - verify(mLogWriter).action( - MetricsEvent.SETTINGS_GESTURES, - MetricsEvent.ACTION_SETTINGS_TILE_CLICK, - SettingsEnums.PAGE_UNKNOWN, - key, - 0); + verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, key); } @Test @@ -98,12 +93,7 @@ public class MetricsFeatureProviderTest { MetricsEvent.SETTINGS_GESTURES); assertThat(loggable).isTrue(); - verify(mLogWriter).action( - MetricsEvent.SETTINGS_GESTURES, - MetricsEvent.ACTION_SETTINGS_TILE_CLICK, - SettingsEnums.PAGE_UNKNOWN, - Intent.ACTION_ASSIST, - 0); + verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, Intent.ACTION_ASSIST); } @Test @@ -116,12 +106,7 @@ public class MetricsFeatureProviderTest { MetricsEvent.SETTINGS_GESTURES); assertThat(loggable).isTrue(); - verify(mLogWriter).action( - MetricsEvent.SETTINGS_GESTURES, - MetricsEvent.ACTION_SETTINGS_TILE_CLICK, - SettingsEnums.PAGE_UNKNOWN, - fragment, - 0); + verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, fragment); } @Test @@ -140,12 +125,7 @@ public class MetricsFeatureProviderTest { final boolean loggable = mProvider.logStartedIntent(intent, MetricsEvent.SETTINGS_GESTURES); assertThat(loggable).isTrue(); - verify(mLogWriter).action( - MetricsEvent.SETTINGS_GESTURES, - MetricsEvent.ACTION_SETTINGS_TILE_CLICK, - SettingsEnums.PAGE_UNKNOWN, - Intent.ACTION_ASSIST, - 0); + verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, Intent.ACTION_ASSIST); } @Test @@ -155,12 +135,7 @@ public class MetricsFeatureProviderTest { final boolean loggable = mProvider.logStartedIntent(intent, MetricsEvent.SETTINGS_GESTURES); assertThat(loggable).isTrue(); - verify(mLogWriter).action( - MetricsEvent.SETTINGS_GESTURES, - MetricsEvent.ACTION_SETTINGS_TILE_CLICK, - SettingsEnums.PAGE_UNKNOWN, - "pkg/cls", - 0); + verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "pkg/cls"); } @Test @@ -171,12 +146,7 @@ public class MetricsFeatureProviderTest { MetricsEvent.SETTINGS_GESTURES, false); assertThat(loggable).isTrue(); - verify(mLogWriter).action( - MetricsEvent.SETTINGS_GESTURES, - MetricsEvent.ACTION_SETTINGS_TILE_CLICK, - SettingsEnums.PAGE_UNKNOWN, - "pkg/cls/personal", - 0); + verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "pkg/cls/personal"); } @Test @@ -187,12 +157,7 @@ public class MetricsFeatureProviderTest { MetricsEvent.SETTINGS_GESTURES, true); assertThat(loggable).isTrue(); - verify(mLogWriter).action( - MetricsEvent.SETTINGS_GESTURES, - MetricsEvent.ACTION_SETTINGS_TILE_CLICK, - SettingsEnums.PAGE_UNKNOWN, - "pkg/cls/work", - 0); + verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, "pkg/cls/work"); } @Test @@ -226,12 +191,7 @@ public class MetricsFeatureProviderTest { MetricsEvent.SETTINGS_GESTURES); assertThat(loggable).isTrue(); - verify(mLogWriter).action( - MetricsEvent.SETTINGS_GESTURES, - MetricsEvent.ACTION_SETTINGS_TILE_CLICK, - SettingsEnums.PAGE_UNKNOWN, - key, - 0); + verify(mLogWriter).clicked(MetricsEvent.SETTINGS_GESTURES, key); } @Test diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index 10ac5d9aed16..f50168250c96 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -72,6 +72,7 @@ public class SystemSettings { Settings.System.SIP_CALL_OPTIONS, Settings.System.SIP_RECEIVE_CALLS, Settings.System.POINTER_SPEED, + Settings.System.VIBRATE_ON, Settings.System.VIBRATE_WHEN_RINGING, Settings.System.RINGTONE, Settings.System.LOCK_TO_APP_ENABLED, diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index e7c7ac0f8d80..f05f63700710 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -83,7 +83,6 @@ public class SettingsBackupTest { Settings.System.SYSTEM_LOCALES, // bug? Settings.System.USER_ROTATION, // backup candidate? Settings.System.VIBRATE_IN_SILENT, // deprecated? - Settings.System.VIBRATE_ON, // candidate for backup? Settings.System.VOLUME_ACCESSIBILITY, // used internally, changing value will // not change volume Settings.System.VOLUME_ALARM, // deprecated since API 2? diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index bfbccb85b13f..7839ec832e2c 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -498,7 +498,6 @@ -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt -packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt -packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt -packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java index 1aaf19e8793f..c50340cfd247 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java @@ -147,10 +147,10 @@ public interface FalsingManager { } /** - * Listener that is alerted when a double tap is required to confirm a single tap. + * Listener that is alerted when an additional tap is required to confirm a single tap. **/ interface FalsingTapListener { - void onDoubleTapRequired(); + void onAdditionalTapRequired(); } /** Passed to {@link FalsingManager#onProximityEvent}. */ diff --git a/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_off.xml b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_off.xml new file mode 100644 index 000000000000..a34712386d52 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_off.xml @@ -0,0 +1,109 @@ +<?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 + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="17" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M7.38 -0.28 C5.69,-2.81 1.5,-8.5 0.01,-7.32 C0.03,-6.22 -0.06,6.16 -0.06,7.78 C1.56,7.81 5.44,7.19 7.37,2.06 C7.37,1.21 7.35,1.69 7.35,1 C7.35,0.09 7.38,0.59 7.38,-0.28c " + android:valueTo="M6 -2.38 C1.81,-6.69 -0.5,-10 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 3,7.75 5.47,4.91 C6.33,3.91 7.21,2.42 7.24,1.13 C7.26,-0.05 6.87,-1.49 6,-2.38c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="33" + android:propertyName="pathData" + android:startOffset="17" + android:valueFrom="M6 -2.38 C1.81,-6.69 -0.5,-10 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 3,7.75 5.47,4.91 C6.33,3.91 7.21,2.42 7.24,1.13 C7.26,-0.05 6.87,-1.49 6,-2.38c " + android:valueTo="M4.94 -3.95 C-0.31,-9.06 0.52,-9.42 -5.75,-3.22 C-8.31,-0.69 -7.05,3 -6.94,3.22 C-3.63,9.31 2.63,9.5 5.94,4.59 C6.61,3.6 7.43,1.16 7.12,0 C6.72,-1.45 6.01,-2.9 4.94,-3.95c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="67" + android:propertyName="pathData" + android:startOffset="50" + android:valueFrom="M4.94 -3.95 C-0.31,-9.06 0.52,-9.42 -5.75,-3.22 C-8.31,-0.69 -7.05,3 -6.94,3.22 C-3.63,9.31 2.63,9.5 5.94,4.59 C6.61,3.6 7.43,1.16 7.12,0 C6.72,-1.45 6.01,-2.9 4.94,-3.95c " + android:valueTo="M3.07 -5.83 C-0.44,-10.25 -2.56,-6.87 -6.11,-2.6 C-7.03,-1.49 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.1,-4.1 3.07,-5.83c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="83" + android:propertyName="pathData" + android:startOffset="117" + android:valueFrom="M3.07 -5.83 C-0.44,-10.25 -2.56,-6.87 -6.11,-2.6 C-7.03,-1.49 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.1,-4.1 3.07,-5.83c " + android:valueTo="M0 -8.18 C-1.81,-7.06 -3.89,-5.21 -5.95,-2.81 C-7.56,-0.94 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.99 0,7.99 C0,6.34 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-6 0,-8.18c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="433" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#edf2eb" + android:fillType="nonZero" + android:pathData=" M7.38 -0.28 C5.69,-2.81 1.5,-8.5 0.01,-7.32 C0.03,-6.22 -0.06,6.16 -0.06,7.78 C1.56,7.81 5.44,7.19 7.37,2.06 C7.37,1.21 7.35,1.69 7.35,1 C7.35,0.09 7.38,0.59 7.38,-0.28c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -7.19 C0,-7.19 2.93,-4.32 4.38,-2.87 C5.25,-2 5.88,-0.62 5.88,1.19 C5.88,4.5 2.8,6.97 0,7 C-2.8,7.03 -6,4.37 -6,1.13 C-6,-0.43 -5.38,-1.9 -4.25,-3.01 C-4.25,-3.01 0,-7.19 0,-7.19 M-5.65 -4.44 C-5.65,-4.44 -5.65,-4.44 -5.65,-4.44 C-7.1,-3.01 -8,-1.04 -8,1.13 C-8,5.48 -4.42,9 0,9 C4.42,9 8,5.48 8,1.13 C8,-1.04 7.1,-3.01 5.65,-4.44 C5.65,-4.44 5.65,-4.44 5.65,-4.44 C5.65,-4.44 0,-10 0,-10 C0,-10 -5.65,-4.44 -5.65,-4.44c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_on.xml b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_on.xml new file mode 100644 index 000000000000..c5609d8e465b --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/qs_invert_colors_icon_on.xml @@ -0,0 +1,109 @@ +<?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 + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="100" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M0 -7.49 C-2.58,-7.49 -7.47,-2.59 -7.47,0.57 C-7.47,0.77 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.55 0,7.55 C0,5.91 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-5.31 0,-7.49c " + android:valueTo="M3.11 -6.83 C-1.37,-10.12 -6.04,-3.87 -7.22,0.09 C-7.26,0.29 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.14,-5.1 3.11,-6.83c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="67" + android:propertyName="pathData" + android:startOffset="100" + android:valueFrom="M3.11 -6.83 C-1.37,-10.12 -6.04,-3.87 -7.22,0.09 C-7.26,0.29 -7.23,1.85 -7.18,2.06 C-5.84,7.25 -0.33,9.23 3.14,6.75 C3.17,5.1 3.17,3.58 3.22,0 C3.25,-3.04 3.14,-5.1 3.11,-6.83c " + android:valueTo="M5 -3.95 C-0.25,-9.87 -0.47,-8.74 -5,-3.63 C-7.94,-0.31 -7.05,3 -6.94,3.22 C-3.63,9.31 2.94,9.19 7,3.59 C7.06,1.94 7,3.19 7.12,0 C7.19,-2 5.06,-2.75 5,-3.95c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="67" + android:propertyName="pathData" + android:startOffset="167" + android:valueFrom="M5 -3.95 C-0.25,-9.87 -0.47,-8.74 -5,-3.63 C-7.94,-0.31 -7.05,3 -6.94,3.22 C-3.63,9.31 2.94,9.19 7,3.59 C7.06,1.94 7,3.19 7.12,0 C7.19,-2 5.06,-2.75 5,-3.95c " + android:valueTo="M4.82 -3.81 C1,-7.87 0.75,-9.37 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 4.38,7.38 6.4,3.22 C6.77,2.46 7,1.69 6.74,0 C6.56,-1.16 5.85,-2.71 4.82,-3.81c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="100" + android:propertyName="pathData" + android:startOffset="233" + android:valueFrom="M4.82 -3.81 C1,-7.87 0.75,-9.37 -1.72,-7.1 C-1.73,-6.22 -1.73,5.88 -1.7,7.16 C0.25,8.45 4.38,7.38 6.4,3.22 C6.77,2.46 7,1.69 6.74,0 C6.56,-1.16 5.85,-2.71 4.82,-3.81c " + android:valueTo="M5.63 -2.97 C3.65,-4.93 0.75,-8.37 0.01,-7.32 C0.03,-6.22 -0.03,5.66 -0.03,7.28 C1.59,7.31 4.13,7.94 6.31,3.44 C6.68,2.66 7,1.37 6.87,0.06 C6.77,-0.84 6.13,-2.47 5.63,-2.97c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="433" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#edf2eb" + android:fillType="nonZero" + android:pathData=" M0 -7.49 C-2.58,-7.49 -7.47,-2.59 -7.47,0.57 C-7.47,0.77 -7.37,0.88 -7.37,1.09 C-7.37,6.31 -2.19,7.55 0,7.55 C0,5.91 -0.01,3.91 -0.01,0 C-0.01,-3.91 0,-5.31 0,-7.49c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M0 -7.19 C0,-7.19 2.93,-4.32 4.38,-2.87 C5.25,-2 5.88,-0.62 5.88,1.19 C5.88,4.5 2.8,6.97 0,7 C-2.8,7.03 -6,4.37 -6,1.13 C-6,-0.43 -5.38,-1.9 -4.25,-3.01 C-4.25,-3.01 0,-7.19 0,-7.19 M-5.65 -4.44 C-5.65,-4.44 -5.65,-4.44 -5.65,-4.44 C-7.1,-3.01 -8,-1.04 -8,1.13 C-8,5.48 -4.42,9 0,9 C4.42,9 8,5.48 8,1.13 C8,-1.04 7.1,-3.01 5.65,-4.44 C5.65,-4.44 5.65,-4.44 5.65,-4.44 C5.65,-4.44 0,-10 0,-10 C0,-10 -5.65,-4.44 -5.65,-4.44c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml b/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml index f239a8dd4291..420ae7044149 100644 --- a/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml +++ b/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml @@ -15,11 +15,45 @@ --> <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="167" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#ffffff" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="167" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#ffffff" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> <target android:name="time_group"> <aapt:attr name="android:animation"> <set android:ordering="together"> <objectAnimator - android:duration="183" + android:duration="333" android:propertyName="translateX" android:startOffset="0" android:valueFrom="0" @@ -38,13 +72,29 @@ <group android:name="_R_G_L_0_G" android:translateX="12" - android:translateY="12"> - <path - android:name="_R_G_L_0_G_D_0_P_0" - android:fillAlpha="1" - android:fillColor="#ffffff" - android:fillType="nonZero" - android:pathData=" M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c M1.5 1.5 C1.5,1.5 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5c " /> + android:translateY="12.469"> + <group + android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0" + android:scaleX="1" + android:scaleY="1"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#000000" + android:fillType="nonZero" + android:pathData=" M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " /> + </group> + <group + android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0" + android:scaleX="1" + android:scaleY="1"> + <path + android:name="_R_G_L_0_G_D_0_P_1" + android:fillAlpha="1" + android:fillColor="#000000" + android:fillType="nonZero" + android:pathData=" M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c " /> + </group> </group> </group> <group android:name="time_group" /> diff --git a/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml b/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml index d46fafa25816..57d7902f59ca 100644 --- a/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml +++ b/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml @@ -15,35 +15,213 @@ --> <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"> - <target android:name="_R_G_L_1_G"> + <target android:name="_R_G_L_0_G_D_0_P_0"> <aapt:attr name="android:animation"> <set android:ordering="together"> <objectAnimator - android:duration="667" - android:pathData="M 12.125,34.75C 12.104,30.958 12.021,15.792 12,12" - android:propertyName="translateXY" - android:propertyXName="translateX" - android:propertyYName="translateY" - android:startOffset="0"> + android:duration="250" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#ffffff" + android:valueTo="#000000" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="383" + android:propertyName="scaleX" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="1.1500000000000001" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.4,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="383" + android:propertyName="scaleY" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="1.1500000000000001" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.4,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="767" + android:propertyName="scaleX" + android:startOffset="383" + android:valueFrom="1.1500000000000001" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="767" + android:propertyName="scaleY" + android:startOffset="383" + android:valueFrom="1.1500000000000001" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="200" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " + android:valueTo="M-1.74 -2.93 C-1.74,-2.93 -2.06,-7.3 -2.06,-7.3 C-2.14,-8.99 -1.15,-10 0,-10 C1.15,-10 2.08,-8.88 2.09,-7.3 C2.09,-7.3 1.74,-3.09 1.74,-3.09 C1.74,-3.09 9.44,2.79 9.44,2.79 C9.44,2.79 9.44,4.79 9.44,4.79 C9.44,4.79 1.69,1.57 1.69,1.57 C1.69,1.57 -1.74,-2.93 -1.74,-2.93c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="217" + android:propertyName="pathData" + android:startOffset="200" + android:valueFrom="M-1.74 -2.93 C-1.74,-2.93 -2.06,-7.3 -2.06,-7.3 C-2.14,-8.99 -1.15,-10 0,-10 C1.15,-10 2.08,-8.88 2.09,-7.3 C2.09,-7.3 1.74,-3.09 1.74,-3.09 C1.74,-3.09 9.44,2.79 9.44,2.79 C9.44,2.79 9.44,4.79 9.44,4.79 C9.44,4.79 1.69,1.57 1.69,1.57 C1.69,1.57 -1.74,-2.93 -1.74,-2.93c " + android:valueTo="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="217" + android:propertyName="pathData" + android:startOffset="417" + android:valueFrom="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " + android:valueTo="M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.35,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="250" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#ffffff" + android:valueTo="#000000" + android:valueType="colorType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> </aapt:attr> </objectAnimator> </set> </aapt:attr> </target> - <target android:name="_R_G_L_0_G"> + <target android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0"> <aapt:attr name="android:animation"> <set android:ordering="together"> <objectAnimator - android:duration="517" - android:pathData="M 12,12C 12.021,8.312 12.104,-6.436999999999999 12.125,-10.125" - android:propertyName="translateXY" - android:propertyXName="translateX" - android:propertyYName="translateY" - android:startOffset="0"> + android:duration="383" + android:propertyName="scaleX" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="1.1500000000000001" + android:valueType="floatType"> <aapt:attr name="android:interpolator"> - <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.4,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="383" + android:propertyName="scaleY" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="1.1500000000000001" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.4,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="767" + android:propertyName="scaleX" + android:startOffset="383" + android:valueFrom="1.1500000000000001" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="767" + android:propertyName="scaleY" + android:startOffset="383" + android:valueFrom="1.1500000000000001" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.199,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="200" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M1.51 1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.49 1.51,1.49c " + android:valueTo="M1.69 1.58 C1.69,1.58 -1.74,-2.92 -1.74,-2.92 C-1.74,-2.92 -9.44,2.79 -9.44,2.79 C-9.44,2.79 -9.44,4.79 -9.44,4.79 C-9.44,4.79 -1.67,1.42 -1.67,1.42 C-1.67,1.42 -1.11,7.28 -1.11,7.28 C-1.11,7.28 -2.94,8.78 -2.94,8.78 C-2.94,8.78 -2.92,10 -2.92,10 C-2.92,10 0,9 0,9 C0,9 2.92,10 2.92,10 C2.92,10 2.91,8.78 2.91,8.78 C2.91,8.78 1.08,7.28 1.08,7.28 C1.08,7.28 1.69,1.58 1.69,1.58c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="217" + android:propertyName="pathData" + android:startOffset="200" + android:valueFrom="M1.69 1.58 C1.69,1.58 -1.74,-2.92 -1.74,-2.92 C-1.74,-2.92 -9.44,2.79 -9.44,2.79 C-9.44,2.79 -9.44,4.79 -9.44,4.79 C-9.44,4.79 -1.67,1.42 -1.67,1.42 C-1.67,1.42 -1.11,7.28 -1.11,7.28 C-1.11,7.28 -2.94,8.78 -2.94,8.78 C-2.94,8.78 -2.92,10 -2.92,10 C-2.92,10 0,9 0,9 C0,9 2.92,10 2.92,10 C2.92,10 2.91,8.78 2.91,8.78 C2.91,8.78 1.08,7.28 1.08,7.28 C1.08,7.28 1.69,1.58 1.69,1.58c " + android:valueTo="M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="217" + android:propertyName="pathData" + android:startOffset="417" + android:valueFrom="M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c " + android:valueTo="M1.51 1.5 C1.51,1.5 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.5 1.51,1.5c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.35,1 1.0,1.0" /> </aapt:attr> </objectAnimator> </set> @@ -53,7 +231,7 @@ <aapt:attr name="android:animation"> <set android:ordering="together"> <objectAnimator - android:duration="683" + android:duration="1167" android:propertyName="translateX" android:startOffset="0" android:valueFrom="0" @@ -70,26 +248,31 @@ android:viewportWidth="24"> <group android:name="_R_G"> <group - android:name="_R_G_L_1_G" - android:translateX="12.125" - android:translateY="34.75"> - <path - android:name="_R_G_L_1_G_D_0_P_0" - android:fillAlpha="1" - android:fillColor="#ffffff" - android:fillType="nonZero" - android:pathData=" M10 4 C10,4 10,2 10,2 C10,2 1.5,-3 1.5,-3 C1.5,-3 1.5,-8.5 1.5,-8.5 C1.5,-9.33 0.83,-10 0,-10 C-0.83,-10 -1.5,-9.33 -1.5,-8.5 C-1.5,-8.5 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5 C1.5,1.5 10,4 10,4c " /> - </group> - <group android:name="_R_G_L_0_G" android:translateX="12" - android:translateY="12"> - <path - android:name="_R_G_L_0_G_D_0_P_0" - android:fillAlpha="1" - android:fillColor="#ffffff" - android:fillType="nonZero" - android:pathData=" M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c M1.5 1.5 C1.5,1.5 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5c " /> + android:translateY="12.469"> + <group + android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0" + android:scaleX="1" + android:scaleY="1"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c " /> + </group> + <group + android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0" + android:scaleX="1" + android:scaleY="1"> + <path + android:name="_R_G_L_0_G_D_0_P_1" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M1.51 1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02 C-1.5,-3.02 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.51,1.49 1.51,1.49c " /> + </group> </group> </group> <group android:name="time_group" /> diff --git a/packages/SystemUI/res/drawable/qs_data_saver_icon_off.xml b/packages/SystemUI/res/drawable/qs_data_saver_icon_off.xml new file mode 100644 index 000000000000..57777a670d33 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_data_saver_icon_off.xml @@ -0,0 +1,84 @@ +<?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. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="133" + android:propertyName="fillAlpha" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="0" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="150" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:pivotY="1" + android:rotation="180" + android:translateX="12" + android:translateY="10.984"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M3 2 C3,2 1,2 1,2 C1,2 1,4 1,4 C1,4 -1,4 -1,4 C-1,4 -1,2 -1,2 C-1,2 -3,2 -3,2 C-3,2 -3,0 -3,0 C-3,0 -1,0 -1,0 C-1,0 -1,-2 -1,-2 C-1,-2 1,-2 1,-2 C1,-2 1,0 1,0 C1,0 3,0 3,0 C3,0 3,2 3,2c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:rotation="1440" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M9.15 4.05 C9.15,4.05 6.55,2.55 6.55,2.55 C6.7,2.13 6.81,1.71 6.89,1.29 C6.96,0.86 7,0.43 7,0 C7,-1.77 6.43,-3.31 5.3,-4.62 C4.17,-5.94 2.73,-6.72 1,-6.95 C1,-6.95 1,-9.95 1,-9.95 C3.57,-9.7 5.71,-8.62 7.43,-6.72 C9.14,-4.82 10,-2.58 10,0 C10,0.7 9.94,1.39 9.81,2.08 C9.69,2.76 9.47,3.42 9.15,4.05c M0 10 C-1.38,10 -2.68,9.74 -3.9,9.21 C-5.12,8.69 -6.17,7.98 -7.07,7.08 C-7.97,6.18 -8.69,5.12 -9.21,3.9 C-9.74,2.68 -10,1.38 -10,0 C-10,-2.58 -9.14,-4.82 -7.42,-6.72 C-5.71,-8.62 -3.57,-9.7 -1,-9.95 C-1,-9.95 -1,-6.95 -1,-6.95 C-2.73,-6.72 -4.17,-5.94 -5.3,-4.62 C-6.43,-3.31 -7,-1.77 -7,0 C-7,1.95 -6.32,3.6 -4.96,4.96 C-3.6,6.32 -1.95,7 0,7 C1.07,7 2.08,6.78 3.04,6.33 C4,5.88 4.82,5.23 5.5,4.4 C5.5,4.4 8.1,5.9 8.1,5.9 C7.15,7.2 5.97,8.21 4.55,8.93 C3.13,9.64 1.62,10 0,10c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_data_saver_icon_on.xml b/packages/SystemUI/res/drawable/qs_data_saver_icon_on.xml new file mode 100644 index 000000000000..b3764137793a --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_data_saver_icon_on.xml @@ -0,0 +1,110 @@ +<?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. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="333" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M1.02 2 C1.02,2 1,2 1,2 C1,2 1,1.98 1,1.98 C1,1.98 -1,1.98 -1,1.98 C-1,1.98 -1,2 -1,2 C-1,2 -1,2 -1,2 C-1,2 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 1,0 1,0 C1,0 1,0 1,0 C1,0 1.02,0 1.02,0 C1.02,0 1.02,2 1.02,2c " + android:valueTo="M3 2 C3,2 1,2 1,2 C1,2 1,4 1,4 C1,4 -1,4 -1,4 C-1,4 -1,2 -1,2 C-1,2 -3,2 -3,2 C-3,2 -3,0 -3,0 C-3,0 -1,0 -1,0 C-1,0 -1,-2 -1,-2 C-1,-2 1,-2 1,-2 C1,-2 1,0 1,0 C1,0 3,0 3,0 C3,0 3,2 3,2c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.2,0.2 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="500" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="613.191" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.086,0.422 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator + android:duration="417" + android:propertyName="rotation" + android:startOffset="500" + android:valueFrom="613.191" + android:valueTo="720" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.334,1.012 0.833,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="933" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:translateX="12" + android:translateY="10.75"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M1.02 2 C1.02,2 1,2 1,2 C1,2 1,1.98 1,1.98 C1,1.98 -1,1.98 -1,1.98 C-1,1.98 -1,2 -1,2 C-1,2 -1,2 -1,2 C-1,2 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 -1,0 -1,0 C-1,0 1,0 1,0 C1,0 1,0 1,0 C1,0 1.02,0 1.02,0 C1.02,0 1.02,2 1.02,2c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:rotation="0" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M9.15 4.05 C9.15,4.05 6.55,2.55 6.55,2.55 C6.7,2.13 6.81,1.71 6.89,1.29 C6.96,0.86 7,0.43 7,0 C7,-1.77 6.43,-3.31 5.3,-4.62 C4.17,-5.94 2.73,-6.72 1,-6.95 C1,-6.95 1,-9.95 1,-9.95 C3.57,-9.7 5.71,-8.62 7.43,-6.72 C9.14,-4.82 10,-2.58 10,0 C10,0.7 9.94,1.39 9.81,2.08 C9.69,2.76 9.47,3.42 9.15,4.05c M0 10 C-1.38,10 -2.68,9.74 -3.9,9.21 C-5.12,8.69 -6.17,7.98 -7.07,7.08 C-7.97,6.18 -8.69,5.12 -9.21,3.9 C-9.74,2.68 -10,1.38 -10,0 C-10,-2.58 -9.14,-4.82 -7.42,-6.72 C-5.71,-8.62 -3.57,-9.7 -1,-9.95 C-1,-9.95 -1,-6.95 -1,-6.95 C-2.73,-6.72 -4.17,-5.94 -5.3,-4.62 C-6.43,-3.31 -7,-1.77 -7,0 C-7,1.95 -6.32,3.6 -4.96,4.96 C-3.6,6.32 -1.95,7 0,7 C1.07,7 2.08,6.78 3.04,6.33 C4,5.88 4.82,5.23 5.5,4.4 C5.5,4.4 8.1,5.9 8.1,5.9 C7.15,7.2 5.97,8.21 4.55,8.93 C3.13,9.64 1.62,10 0,10c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_extra_dim_icon_off.xml b/packages/SystemUI/res/drawable/qs_extra_dim_icon_off.xml new file mode 100644 index 000000000000..b0f85e7714c6 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_extra_dim_icon_off.xml @@ -0,0 +1,197 @@ +<?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. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="strokeColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#ffffff" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#ffffff" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M93 39.5 C93,39.55 93,41.52 93,44 C93,46.49 93,48.58 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 84.31,45 84.31,45 C84.31,45 84.31,43 84.31,43 C84.31,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,52.69 94,52.69 C94,52.69 92,52.69 92,52.69 C92,52.69 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 101.75,43 101.75,43 C101.75,43 101.75,45 101.75,45 C101.75,45 100,45 100,45c M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c M87.57 50.87 C87.57,50.87 86.16,49.46 86.16,49.46 C86.16,49.46 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 87.57,50.87 87.57,50.87c M99.9 49.43 C99.9,49.43 98.49,50.84 98.49,50.84 C98.49,50.84 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 99.9,49.43 99.9,49.43c M98.52 37.13 C98.52,37.13 99.93,38.54 99.93,38.54 C99.93,38.54 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 98.52,37.13 98.52,37.13c " + android:valueTo="M93 39.5 C95.49,39.5 97.5,41.52 97.5,44 C97.5,46.49 95.49,48.5 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 82,45 82,45 C82,45 82,43 82,43 C82,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,55 94,55 C94,55 92,55 92,55 C92,55 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 104,43 104,43 C104,43 104,45 104,45 C104,45 100,45 100,45c M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c M86.64 51.78 C86.64,51.78 85.22,50.36 85.22,50.36 C85.22,50.36 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 86.64,51.78 86.64,51.78c M100.78 50.36 C100.78,50.36 99.36,51.78 99.36,51.78 C99.36,51.78 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 100.78,50.36 100.78,50.36c M99.36 36.22 C99.36,36.22 100.78,37.64 100.78,37.64 C100.78,37.64 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 99.36,36.22 99.36,36.22c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#ffffff" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 84.31,45 84.31,45 C84.31,45 84.31,43 84.31,43 C84.31,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,52.69 94,52.69 C94,52.69 92,52.69 92,52.69 C92,52.69 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 101.75,43 101.75,43 C101.75,43 101.75,45 101.75,45 C101.75,45 100,45 100,45c " + android:valueTo="M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 82,45 82,45 C82,45 82,43 82,43 C82,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,55 94,55 C94,55 92,55 92,55 C92,55 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 104,43 104,43 C104,43 104,45 104,45 C104,45 100,45 100,45c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#ffffff" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c M87.57 50.87 C87.57,50.87 86.16,49.46 86.16,49.46 C86.16,49.46 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 87.57,50.87 87.57,50.87c M99.9 49.43 C99.9,49.43 98.49,50.84 98.49,50.84 C98.49,50.84 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 99.9,49.43 99.9,49.43c M98.52 37.13 C98.52,37.13 99.93,38.54 99.93,38.54 C99.93,38.54 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 98.52,37.13 98.52,37.13c " + android:valueTo="M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c M86.64 51.78 C86.64,51.78 85.22,50.36 85.22,50.36 C85.22,50.36 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 86.64,51.78 86.64,51.78c M100.78 50.36 C100.78,50.36 99.36,51.78 99.36,51.78 C99.36,51.78 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 100.78,50.36 100.78,50.36c M99.36 36.22 C99.36,36.22 100.78,37.64 100.78,37.64 C100.78,37.64 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 99.36,36.22 99.36,36.22c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="517" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:translateX="-81" + android:translateY="-32"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:pathData=" M93 40 C95.21,40 97,41.79 97,44 C97,46.21 95.21,48 93,48 C90.79,48 89,46.21 89,44 C89,41.79 90.79,40 93,40c " + android:strokeAlpha="1" + android:strokeColor="#000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="-81" + android:translateY="-32"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#000000" + android:fillType="nonZero" + android:pathData=" M93 39.5 C93,39.55 93,41.52 93,44 C93,46.49 93,48.58 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="1" + android:fillColor="#000000" + android:fillType="nonZero" + android:pathData=" M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c " /> + <path + android:name="_R_G_L_0_G_D_2_P_0" + android:fillAlpha="1" + android:fillColor="#000000" + android:fillType="nonZero" + android:pathData=" M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_extra_dim_icon_on.xml b/packages/SystemUI/res/drawable/qs_extra_dim_icon_on.xml new file mode 100644 index 000000000000..5546b6f39813 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_extra_dim_icon_on.xml @@ -0,0 +1,197 @@ +<?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. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="strokeColor" + android:startOffset="0" + android:valueFrom="#ffffff" + android:valueTo="#000000" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#ffffff" + android:valueTo="#000000" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M93 39.5 C95.49,39.5 97.5,41.52 97.5,44 C97.5,46.49 95.49,48.5 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 82,45 82,45 C82,45 82,43 82,43 C82,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,55 94,55 C94,55 92,55 92,55 C92,55 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 104,43 104,43 C104,43 104,45 104,45 C104,45 100,45 100,45c M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c M86.64 51.78 C86.64,51.78 85.22,50.36 85.22,50.36 C85.22,50.36 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 86.64,51.78 86.64,51.78c M100.78 50.36 C100.78,50.36 99.36,51.78 99.36,51.78 C99.36,51.78 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 100.78,50.36 100.78,50.36c M99.36 36.22 C99.36,36.22 100.78,37.64 100.78,37.64 C100.78,37.64 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 99.36,36.22 99.36,36.22c " + android:valueTo="M93 39.5 C93,39.55 93,41.52 93,44 C93,46.49 93,48.58 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 84.31,45 84.31,45 C84.31,45 84.31,43 84.31,43 C84.31,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,52.69 94,52.69 C94,52.69 92,52.69 92,52.69 C92,52.69 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 101.75,43 101.75,43 C101.75,43 101.75,45 101.75,45 C101.75,45 100,45 100,45c M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c M87.57 50.87 C87.57,50.87 86.16,49.46 86.16,49.46 C86.16,49.46 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 87.57,50.87 87.57,50.87c M99.9 49.43 C99.9,49.43 98.49,50.84 98.49,50.84 C98.49,50.84 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 99.9,49.43 99.9,49.43c M98.52 37.13 C98.52,37.13 99.93,38.54 99.93,38.54 C99.93,38.54 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 98.52,37.13 98.52,37.13c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#ffffff" + android:valueTo="#000000" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 82,45 82,45 C82,45 82,43 82,43 C82,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,55 94,55 C94,55 92,55 92,55 C92,55 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 104,43 104,43 C104,43 104,45 104,45 C104,45 100,45 100,45c " + android:valueTo="M94 37 C94,37 92,37 92,37 C92,37 92,35.44 92,35.44 C92,35.44 94,35.44 94,35.44 C94,35.44 94,37 94,37c M86 43 C86,43 86,45 86,45 C86,45 84.31,45 84.31,45 C84.31,45 84.31,43 84.31,43 C84.31,43 86,43 86,43c M92 51 C92,51 94,51 94,51 C94,51 94,52.69 94,52.69 C94,52.69 92,52.69 92,52.69 C92,52.69 92,51 92,51c M100 45 C100,45 100,43 100,43 C100,43 101.75,43 101.75,43 C101.75,43 101.75,45 101.75,45 C101.75,45 100,45 100,45c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#ffffff" + android:valueTo="#000000" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="350" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c M86.64 51.78 C86.64,51.78 85.22,50.36 85.22,50.36 C85.22,50.36 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 86.64,51.78 86.64,51.78c M100.78 50.36 C100.78,50.36 99.36,51.78 99.36,51.78 C99.36,51.78 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 100.78,50.36 100.78,50.36c M99.36 36.22 C99.36,36.22 100.78,37.64 100.78,37.64 C100.78,37.64 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 99.36,36.22 99.36,36.22c " + android:valueTo="M86.07 38.57 C86.07,38.57 87.48,37.16 87.48,37.16 C87.48,37.16 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 86.07,38.57 86.07,38.57c M87.57 50.87 C87.57,50.87 86.16,49.46 86.16,49.46 C86.16,49.46 87.34,48.24 87.34,48.24 C87.34,48.24 88.76,49.66 88.76,49.66 C88.76,49.66 87.57,50.87 87.57,50.87c M99.9 49.43 C99.9,49.43 98.49,50.84 98.49,50.84 C98.49,50.84 97.24,49.66 97.24,49.66 C97.24,49.66 98.66,48.24 98.66,48.24 C98.66,48.24 99.9,49.43 99.9,49.43c M98.52 37.13 C98.52,37.13 99.93,38.54 99.93,38.54 C99.93,38.54 98.66,39.76 98.66,39.76 C98.66,39.76 97.24,38.34 97.24,38.34 C97.24,38.34 98.52,37.13 98.52,37.13c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="517" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:translateX="-81" + android:translateY="-32"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:pathData=" M93 40 C95.21,40 97,41.79 97,44 C97,46.21 95.21,48 93,48 C90.79,48 89,46.21 89,44 C89,41.79 90.79,40 93,40c " + android:strokeAlpha="1" + android:strokeColor="#ffffff" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="-81" + android:translateY="-32"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M93 39.5 C95.49,39.5 97.5,41.52 97.5,44 C97.5,46.49 95.49,48.5 93,48.5 C90.52,48.5 88.5,46.49 88.5,44 C88.5,41.52 90.52,39.5 93,39.5c " /> + <path + android:name="_R_G_L_0_G_D_1_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M94 37 C94,37 92,37 92,37 C92,37 92,33 92,33 C92,33 94,33 94,33 C94,33 94,37 94,37c " /> + <path + android:name="_R_G_L_0_G_D_2_P_0" + android:fillAlpha="1" + android:fillColor="#ffffff" + android:fillType="nonZero" + android:pathData=" M85.22 37.64 C85.22,37.64 86.64,36.22 86.64,36.22 C86.64,36.22 88.76,38.34 88.76,38.34 C88.76,38.34 87.34,39.76 87.34,39.76 C87.34,39.76 85.22,37.64 85.22,37.64c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_flashlight_icon_off.xml b/packages/SystemUI/res/drawable/qs_flashlight_icon_off.xml new file mode 100644 index 000000000000..157e67a6e8e2 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_flashlight_icon_off.xml @@ -0,0 +1,84 @@ +<?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 + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M0 2.02 C0.42,2.02 0.77,1.88 1.05,1.59 C1.35,1.29 1.5,0.94 1.5,0.52 C1.5,0.1 1.35,-0.25 1.05,-0.53 C0.77,-0.83 0.42,-0.98 0,-0.98 C-0.42,-0.98 -0.78,-0.83 -1.08,-0.53 C-1.36,-0.25 -1.5,0.1 -1.5,0.52 C-1.5,0.94 -1.36,1.29 -1.08,1.59 C-0.78,1.88 -0.42,2.02 0,2.02c M0 8.62 C-0.42,8.62 -2.78,8.82 -3.07,8.54 C-3.36,8.24 -3.37,-1.87 -3.37,-2.28 C-4.25,-2.69 -4.29,-5.2 -4.01,-5.48 C-3.71,-5.78 -0.42,-5.75 0,-5.75 C0.42,-5.75 4.39,-5.78 4.36,-5.36 C4.22,-2.97 4.47,-3.03 3.34,-1.78 C3.07,-1.48 3.32,8.21 3.02,8.51 C2.74,8.79 0.42,8.62 0,8.62c " + android:valueTo="M0 3 C0.42,3 0.77,2.86 1.05,2.58 C1.35,2.28 1.5,1.92 1.5,1.5 C1.5,1.08 1.35,0.73 1.05,0.45 C0.77,0.15 0.42,0 0,0 C-0.42,0 -0.78,0.15 -1.08,0.45 C-1.36,0.73 -1.5,1.08 -1.5,1.5 C-1.5,1.92 -1.36,2.28 -1.08,2.58 C-0.78,2.86 -0.42,3 0,3c M0 8.62 C-0.42,8.62 -2.24,8.83 -2.54,8.55 C-2.83,8.25 -2.86,9.1 -2.86,8.69 C-2.86,8.27 -3.02,8.85 -2.74,8.57 C-2.44,8.26 -0.43,8.31 -0.02,8.31 C0.4,8.31 2.4,8.17 2.68,8.47 C2.98,8.75 2.93,8.33 2.93,8.75 C2.93,9.17 3.32,8.21 3.02,8.51 C2.74,8.79 0.42,8.62 0,8.62c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="1000" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:pivotY="24" + android:scaleX="0.33332999999999996" + android:scaleY="0.33332999999999996" + android:translateX="12" + android:translateY="-4"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#1f1f1f" + android:fillType="nonZero" + android:pathData=" M-12 -15 C-12,-15 12,-15 12,-15 C12,-15 12,-13.8 12,-13.8 C12,-13.8 6,-4.8 6,-4.8 C6,-4.8 6,24 6,24 C6,24 -6,24 -6,24 C-6,24 -6,-4.8 -6,-4.8 C-6,-4.8 -12,-13.8 -12,-13.8 C-12,-13.8 -12,-15 -12,-15c M12 -21 C12,-21 -12,-21 -12,-21 C-12,-21 -12,-24 -12,-24 C-12,-24 12,-24 12,-24 C12,-24 12,-21 12,-21c M-12 -3 C-12,-3 -12,30 -12,30 C-12,30 12,30 12,30 C12,30 12,-3 12,-3 C12,-3 18,-12 18,-12 C18,-12 18,-30 18,-30 C18,-30 -18,-30 -18,-30 C-18,-30 -18,-12 -18,-12 C-18,-12 -12,-3 -12,-3c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#1f1f1f" + android:fillType="nonZero" + android:pathData=" M0 2.02 C0.42,2.02 0.77,1.88 1.05,1.59 C1.35,1.29 1.5,0.94 1.5,0.52 C1.5,0.1 1.35,-0.25 1.05,-0.53 C0.77,-0.83 0.42,-0.98 0,-0.98 C-0.42,-0.98 -0.78,-0.83 -1.08,-0.53 C-1.36,-0.25 -1.5,0.1 -1.5,0.52 C-1.5,0.94 -1.36,1.29 -1.08,1.59 C-0.78,1.88 -0.42,2.02 0,2.02c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_flashlight_icon_on.xml b/packages/SystemUI/res/drawable/qs_flashlight_icon_on.xml new file mode 100644 index 000000000000..22f5e00dad69 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_flashlight_icon_on.xml @@ -0,0 +1,84 @@ +<?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 + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M0 3 C0.42,3 0.77,2.86 1.05,2.58 C1.35,2.28 1.5,1.92 1.5,1.5 C1.5,1.08 1.35,0.73 1.05,0.45 C0.77,0.15 0.42,0 0,0 C-0.42,0 -0.78,0.15 -1.08,0.45 C-1.36,0.73 -1.5,1.08 -1.5,1.5 C-1.5,1.92 -1.36,2.28 -1.08,2.58 C-0.78,2.86 -0.42,3 0,3c M0 8.62 C-0.42,8.62 -2.24,8.83 -2.54,8.55 C-2.83,8.25 -2.86,9.1 -2.86,8.69 C-2.86,8.27 -3.02,8.85 -2.74,8.57 C-2.44,8.26 -0.43,8.31 -0.02,8.31 C0.4,8.31 2.4,8.17 2.68,8.47 C2.98,8.75 2.93,8.33 2.93,8.75 C2.93,9.17 3.32,8.21 3.02,8.51 C2.74,8.79 0.42,8.62 0,8.62c " + android:valueTo="M0 2.02 C0.42,2.02 0.77,1.88 1.05,1.59 C1.35,1.29 1.5,0.94 1.5,0.52 C1.5,0.1 1.35,-0.25 1.05,-0.53 C0.77,-0.83 0.42,-0.98 0,-0.98 C-0.42,-0.98 -0.78,-0.83 -1.08,-0.53 C-1.36,-0.25 -1.5,0.1 -1.5,0.52 C-1.5,0.94 -1.36,1.29 -1.08,1.59 C-0.78,1.88 -0.42,2.02 0,2.02c M0 8.62 C-0.42,8.62 -2.78,8.82 -3.07,8.54 C-3.36,8.24 -3.37,-1.87 -3.37,-2.28 C-4.25,-2.69 -4.29,-5.2 -4.01,-5.48 C-3.71,-5.78 -0.42,-5.75 0,-5.75 C0.42,-5.75 4.39,-5.78 4.36,-5.36 C4.22,-2.97 4.47,-3.03 3.34,-1.78 C3.07,-1.48 3.32,8.21 3.02,8.51 C2.74,8.79 0.42,8.62 0,8.62c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.1,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="1000" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:pivotY="24" + android:scaleX="0.33332999999999996" + android:scaleY="0.33332999999999996" + android:translateX="12" + android:translateY="-4"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#1f1f1f" + android:fillType="nonZero" + android:pathData=" M-12 -15 C-12,-15 12,-15 12,-15 C12,-15 12,-13.8 12,-13.8 C12,-13.8 6,-4.8 6,-4.8 C6,-4.8 6,24 6,24 C6,24 -6,24 -6,24 C-6,24 -6,-4.8 -6,-4.8 C-6,-4.8 -12,-13.8 -12,-13.8 C-12,-13.8 -12,-15 -12,-15c M12 -21 C12,-21 -12,-21 -12,-21 C-12,-21 -12,-24 -12,-24 C-12,-24 12,-24 12,-24 C12,-24 12,-21 12,-21c M-12 -3 C-12,-3 -12,30 -12,30 C-12,30 12,30 12,30 C12,30 12,-3 12,-3 C12,-3 18,-12 18,-12 C18,-12 18,-30 18,-30 C18,-30 -18,-30 -18,-30 C-18,-30 -18,-12 -18,-12 C-18,-12 -12,-3 -12,-3c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#1f1f1f" + android:fillType="nonZero" + android:pathData=" M0 3 C0.42,3 0.77,2.86 1.05,2.58 C1.35,2.28 1.5,1.92 1.5,1.5 C1.5,1.08 1.35,0.73 1.05,0.45 C0.77,0.15 0.42,0 0,0 C-0.42,0 -0.78,0.15 -1.08,0.45 C-1.36,0.73 -1.5,1.08 -1.5,1.5 C-1.5,1.92 -1.36,2.28 -1.08,2.58 C-0.78,2.86 -0.42,3 0,3c " /> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_nightlight_icon_off.xml b/packages/SystemUI/res/drawable/qs_nightlight_icon_off.xml new file mode 100644 index 000000000000..0769a85dae49 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_nightlight_icon_off.xml @@ -0,0 +1,94 @@ +<?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. +--> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="283" + android:propertyName="translateY" + android:startOffset="0" + android:valueFrom="12.125" + android:valueTo="12.312" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="283" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="-45" + android:valueTo="0" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="300" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G_T_1" + android:rotation="-45" + android:translateX="12.875" + android:translateY="12.125"> + <group + android:name="_R_G_L_0_G" + android:translateX="-2.375"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:pathData=" M3.33 8.62 C2.09,8.68 0.59,8.47 -0.64,7.95 C-1.65,7.53 -2.75,6.9 -3.59,6.09 C-4.38,5.32 -5.19,4.17 -5.61,3.08 C-6.04,1.99 -6.25,0.83 -6.25,-0.39 C-6.25,-1.72 -5.98,-2.85 -5.55,-3.94 C-5.13,-5.02 -4.37,-6 -3.59,-6.78 C-2.63,-7.75 -1.63,-8.28 -0.52,-8.77 C0.48,-9.2 2.04,-9.41 3.15,-9.38 C4.22,-9.35 4.94,-9.16 5.81,-8.79 C5.95,-8.73 6.28,-8.6 6.18,-8.55 C3.44,-6.63 1.83,-3.66 1.83,-0.39 C1.83,2.53 3.47,5.72 6.15,7.86 C6.16,7.9 6.03,7.97 5.91,8.04 C5.12,8.44 4.31,8.56 3.33,8.62c " + android:strokeAlpha="1" + android:strokeColor="#ffffff" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" /> + </group> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_nightlight_icon_on.xml b/packages/SystemUI/res/drawable/qs_nightlight_icon_on.xml new file mode 100644 index 000000000000..5ffe262d8077 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_nightlight_icon_on.xml @@ -0,0 +1,94 @@ +<?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. +--> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_0_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="400" + android:propertyName="translateY" + android:startOffset="0" + android:valueFrom="12.312" + android:valueTo="12.125" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="400" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="-45" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_0_G_T_1" + android:rotation="0" + android:translateX="12.875" + android:translateY="12.312"> + <group + android:name="_R_G_L_0_G" + android:translateX="-2.375"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:pathData=" M3.33 8.62 C2.09,8.68 0.59,8.47 -0.64,7.95 C-1.65,7.53 -2.75,6.9 -3.59,6.09 C-4.38,5.32 -5.19,4.17 -5.61,3.08 C-6.04,1.99 -6.25,0.83 -6.25,-0.39 C-6.25,-1.72 -5.98,-2.85 -5.55,-3.94 C-5.13,-5.02 -4.37,-6 -3.6,-6.78 C-2.63,-7.75 -1.63,-8.28 -0.52,-8.77 C0.48,-9.2 2.04,-9.41 3.14,-9.38 C4.22,-9.35 4.94,-9.16 5.81,-8.79 C5.94,-8.73 6.28,-8.6 6.18,-8.55 C3.44,-6.63 1.83,-3.66 1.83,-0.39 C1.83,2.53 3.47,5.72 6.15,7.86 C6.16,7.9 6.03,7.97 5.91,8.04 C5.13,8.43 4.31,8.56 3.33,8.62c " + android:strokeAlpha="1" + android:strokeColor="#ffffff" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" /> + </group> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_screen_record_icon_off.xml b/packages/SystemUI/res/drawable/qs_screen_record_icon_off.xml new file mode 100644 index 000000000000..b3bdd361398d --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_screen_record_icon_off.xml @@ -0,0 +1,218 @@ +<?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. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#edf2eb" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M3.5 -2.5 C3.5,-2.5 3.5,2.5 3.5,2.5 C3.5,3.05 3.05,3.5 2.5,3.5 C2.5,3.5 -2.5,3.5 -2.5,3.5 C-3.05,3.5 -3.5,3.05 -3.5,2.5 C-3.5,2.5 -3.5,-2.5 -3.5,-2.5 C-3.5,-3.05 -3.05,-3.5 -2.5,-3.5 C-2.5,-3.5 2.5,-3.5 2.5,-3.5 C3.05,-3.5 3.5,-3.05 3.5,-2.5c " + android:valueTo="M3.5 0 C3.5,0.97 3.11,1.84 2.48,2.48 C1.84,3.11 0.97,3.5 0,3.5 C-0.97,3.5 -1.84,3.11 -2.47,2.48 C-3.11,1.84 -3.5,0.97 -3.5,0 C-3.5,-0.97 -3.11,-1.84 -2.47,-2.47 C-1.84,-3.11 -0.97,-3.5 0,-3.5 C0.97,-3.5 1.84,-3.11 2.48,-2.47 C3.11,-1.84 3.5,-0.97 3.5,0c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="90" + android:valueTo="0" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#edf2eb" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#edf2eb" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_2"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#edf2eb" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_3"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#000000" + android:valueTo="#edf2eb" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="1000" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:rotation="90" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#000000" + android:fillType="nonZero" + android:pathData=" M3.5 -2.5 C3.5,-2.5 3.5,2.5 3.5,2.5 C3.5,3.05 3.05,3.5 2.5,3.5 C2.5,3.5 -2.5,3.5 -2.5,3.5 C-3.05,3.5 -3.5,3.05 -3.5,2.5 C-3.5,2.5 -3.5,-2.5 -3.5,-2.5 C-3.5,-3.05 -3.05,-3.5 -2.5,-3.5 C-2.5,-3.5 2.5,-3.5 2.5,-3.5 C3.05,-3.5 3.5,-3.05 3.5,-2.5c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <group + android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0" + android:rotation="0"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#000000" + android:fillType="nonZero" + android:pathData=" M6.6 -4.47 C6.6,-4.47 8.04,-5.91 8.04,-5.91 C9.26,-4.26 9.99,-2.21 10,0.01 C10,2.23 9.26,4.27 8.04,5.93 C8.04,5.93 6.6,4.49 6.6,4.49 C7.47,3.2 7.99,1.66 7.99,0 C7.99,-1.65 7.47,-3.19 6.6,-4.47c " /> + </group> + <group + android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0" + android:rotation="0"> + <path + android:name="_R_G_L_0_G_D_0_P_1" + android:fillAlpha="1" + android:fillColor="#000000" + android:fillType="nonZero" + android:pathData=" M-8.01 0 C-8.01,1.67 -7.49,3.22 -6.61,4.5 C-6.61,4.5 -8.04,5.93 -8.04,5.93 C-9.27,4.27 -10,2.22 -10,0 C-10,-2.22 -9.27,-4.26 -8.05,-5.92 C-8.05,-5.92 -6.62,-4.49 -6.62,-4.49 C-7.49,-3.21 -8.01,-1.66 -8.01,0c " /> + </group> + <group + android:name="_R_G_L_0_G_D_0_P_2_G_0_T_0" + android:rotation="0"> + <path + android:name="_R_G_L_0_G_D_0_P_2" + android:fillAlpha="1" + android:fillColor="#000000" + android:fillType="nonZero" + android:pathData=" M-0.01 8 C1.66,8 3.2,7.48 4.48,6.61 C4.48,6.61 5.91,8.05 5.91,8.05 C4.25,9.27 2.21,10 -0.01,10 C-2.22,10 -4.26,9.27 -5.92,8.05 C-5.92,8.05 -4.48,6.61 -4.48,6.61 C-3.21,7.48 -1.67,8 -0.01,8c " /> + </group> + <group + android:name="_R_G_L_0_G_D_0_P_3_G_0_T_0" + android:rotation="0"> + <path + android:name="_R_G_L_0_G_D_0_P_3" + android:fillAlpha="1" + android:fillColor="#000000" + android:fillType="nonZero" + android:pathData=" M-5.93 -8.04 C-4.27,-9.27 -2.23,-10 -0.01,-10 C2.22,-10 4.26,-9.27 5.94,-8.03 C5.94,-8.03 4.5,-6.59 4.5,-6.59 C3.21,-7.47 1.67,-7.99 0,-7.99 C-1.67,-7.99 -3.21,-7.47 -4.49,-6.6 C-4.49,-6.6 -5.93,-8.04 -5.93,-8.04c " /> + </group> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/qs_screen_record_icon_on.xml b/packages/SystemUI/res/drawable/qs_screen_record_icon_on.xml new file mode 100644 index 000000000000..facbef5e4348 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_screen_record_icon_on.xml @@ -0,0 +1,286 @@ +<?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. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#edf2eb" + android:valueTo="#000000" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="pathData" + android:startOffset="0" + android:valueFrom="M3.5 0 C3.5,0.97 3.11,1.84 2.48,2.48 C1.84,3.11 0.97,3.5 0,3.5 C-0.97,3.5 -1.84,3.11 -2.47,2.48 C-3.11,1.84 -3.5,0.97 -3.5,0 C-3.5,-0.97 -3.11,-1.84 -2.47,-2.47 C-1.84,-3.11 -0.97,-3.5 0,-3.5 C0.97,-3.5 1.84,-3.11 2.48,-2.47 C3.11,-1.84 3.5,-0.97 3.5,0c " + android:valueTo="M3.5 -2.5 C3.5,-2.5 3.5,2.5 3.5,2.5 C3.5,3.05 3.05,3.5 2.5,3.5 C2.5,3.5 -2.5,3.5 -2.5,3.5 C-3.05,3.5 -3.5,3.05 -3.5,2.5 C-3.5,2.5 -3.5,-2.5 -3.5,-2.5 C-3.5,-3.05 -3.05,-3.5 -2.5,-3.5 C-2.5,-3.5 2.5,-3.5 2.5,-3.5 C3.05,-3.5 3.5,-3.05 3.5,-2.5c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="90" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#edf2eb" + android:valueTo="#000000" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="833" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="180" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,0.53 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#edf2eb" + android:valueTo="#000000" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="833" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="180" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,0.53 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_2"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#edf2eb" + android:valueTo="#000000" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_2_G_0_T_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="833" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="180" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,0.53 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_3"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="417" + android:propertyName="fillColor" + android:startOffset="0" + android:valueFrom="#edf2eb" + android:valueTo="#000000" + android:valueType="colorType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_3_G_0_T_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="833" + android:propertyName="rotation" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="180" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.2,0.53 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator + android:duration="1000" + android:propertyName="translateX" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType" /> + </set> + </aapt:attr> + </target> + <aapt:attr name="android:drawable"> + <vector + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group android:name="_R_G"> + <group + android:name="_R_G_L_1_G" + android:rotation="0" + android:translateX="12" + android:translateY="12"> + <path + android:name="_R_G_L_1_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#edf2eb" + android:fillType="nonZero" + android:pathData=" M3.5 0 C3.5,0.97 3.11,1.84 2.48,2.48 C1.84,3.11 0.97,3.5 0,3.5 C-0.97,3.5 -1.84,3.11 -2.47,2.48 C-3.11,1.84 -3.5,0.97 -3.5,0 C-3.5,-0.97 -3.11,-1.84 -2.47,-2.47 C-1.84,-3.11 -0.97,-3.5 0,-3.5 C0.97,-3.5 1.84,-3.11 2.48,-2.47 C3.11,-1.84 3.5,-0.97 3.5,0c " /> + </group> + <group + android:name="_R_G_L_0_G" + android:translateX="12" + android:translateY="12"> + <group + android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0" + android:rotation="0"> + <path + android:name="_R_G_L_0_G_D_0_P_0" + android:fillAlpha="1" + android:fillColor="#edf2eb" + android:fillType="nonZero" + android:pathData=" M6.6 -4.47 C6.6,-4.47 8.04,-5.91 8.04,-5.91 C9.26,-4.26 9.99,-2.21 10,0.01 C10,2.23 9.26,4.27 8.04,5.93 C8.04,5.93 6.6,4.49 6.6,4.49 C7.47,3.2 7.99,1.66 7.99,0 C7.99,-1.65 7.47,-3.19 6.6,-4.47c " /> + </group> + <group + android:name="_R_G_L_0_G_D_0_P_1_G_0_T_0" + android:rotation="0"> + <path + android:name="_R_G_L_0_G_D_0_P_1" + android:fillAlpha="1" + android:fillColor="#edf2eb" + android:fillType="nonZero" + android:pathData=" M-8.01 0 C-8.01,1.67 -7.49,3.22 -6.61,4.5 C-6.61,4.5 -8.04,5.93 -8.04,5.93 C-9.27,4.27 -10,2.22 -10,0 C-10,-2.22 -9.27,-4.26 -8.05,-5.92 C-8.05,-5.92 -6.62,-4.49 -6.62,-4.49 C-7.49,-3.21 -8.01,-1.66 -8.01,0c " /> + </group> + <group + android:name="_R_G_L_0_G_D_0_P_2_G_0_T_0" + android:rotation="0"> + <path + android:name="_R_G_L_0_G_D_0_P_2" + android:fillAlpha="1" + android:fillColor="#edf2eb" + android:fillType="nonZero" + android:pathData=" M-0.01 8 C1.66,8 3.2,7.48 4.48,6.61 C4.48,6.61 5.91,8.05 5.91,8.05 C4.25,9.27 2.21,10 -0.01,10 C-2.22,10 -4.26,9.27 -5.92,8.05 C-5.92,8.05 -4.48,6.61 -4.48,6.61 C-3.21,7.48 -1.67,8 -0.01,8c " /> + </group> + <group + android:name="_R_G_L_0_G_D_0_P_3_G_0_T_0" + android:rotation="0"> + <path + android:name="_R_G_L_0_G_D_0_P_3" + android:fillAlpha="1" + android:fillColor="#edf2eb" + android:fillType="nonZero" + android:pathData=" M-5.93 -8.04 C-4.27,-9.27 -2.23,-10 -0.01,-10 C2.22,-10 4.26,-9.27 5.94,-8.03 C5.94,-8.03 4.5,-6.59 4.5,-6.59 C3.21,-7.47 1.67,-7.99 0,-7.99 C-1.67,-7.99 -3.21,-7.47 -4.49,-6.6 C-4.49,-6.6 -5.93,-8.04 -5.93,-8.04c " /> + </group> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> +</animated-vector> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 11dbe4ad417a..9e8bef06270b 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -245,6 +245,10 @@ <color name="dream_overlay_aqi_very_unhealthy">#AD1457</color> <color name="dream_overlay_aqi_hazardous">#880E4F</color> <color name="dream_overlay_aqi_unknown">#BDC1C6</color> + + <!-- Dream overlay text shadows --> <color name="dream_overlay_clock_key_text_shadow_color">#4D000000</color> <color name="dream_overlay_clock_ambient_text_shadow_color">#4D000000</color> + <color name="dream_overlay_status_bar_key_text_shadow_color">#66000000</color> + <color name="dream_overlay_status_bar_ambient_text_shadow_color">#59000000</color> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 2e578524587f..6592e143dc1b 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1560,10 +1560,20 @@ <dimen name="broadcast_dialog_btn_text_size">16sp</dimen> <dimen name="broadcast_dialog_btn_minHeight">44dp</dimen> <dimen name="broadcast_dialog_margin">16dp</dimen> + + <!-- Shadow for dream overlay clock complication --> <dimen name="dream_overlay_clock_key_text_shadow_dx">0dp</dimen> <dimen name="dream_overlay_clock_key_text_shadow_dy">0dp</dimen> - <dimen name="dream_overlay_clock_key_text_shadow_radius">5dp</dimen> + <dimen name="dream_overlay_clock_key_text_shadow_radius">3dp</dimen> <dimen name="dream_overlay_clock_ambient_text_shadow_dx">0dp</dimen> <dimen name="dream_overlay_clock_ambient_text_shadow_dy">0dp</dimen> <dimen name="dream_overlay_clock_ambient_text_shadow_radius">1dp</dimen> + + <!-- Shadow for dream overlay status bar complications --> + <dimen name="dream_overlay_status_bar_key_text_shadow_dx">0.5dp</dimen> + <dimen name="dream_overlay_status_bar_key_text_shadow_dy">0.5dp</dimen> + <dimen name="dream_overlay_status_bar_key_text_shadow_radius">1dp</dimen> + <dimen name="dream_overlay_status_bar_ambient_text_shadow_dx">0.5dp</dimen> + <dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen> + <dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen> </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 34e2e83d7643..c2e74456c032 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -23,6 +23,7 @@ import android.annotation.SuppressLint import android.app.compat.ChangeIdStateCache.invalidate import android.content.Context import android.graphics.Canvas +import android.text.Layout import android.text.TextUtils import android.text.format.DateFormat import android.util.AttributeSet @@ -78,6 +79,8 @@ class AnimatableClockView @JvmOverloads constructor( private var textAnimator: TextAnimator? = null private var onTextAnimatorInitialized: Runnable? = null + @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = + { layout, invalidateCb -> TextAnimator(layout, invalidateCb) } @VisibleForTesting var isAnimationEnabled: Boolean = true @VisibleForTesting var timeOverrideInMillis: Long? = null @@ -174,7 +177,7 @@ class AnimatableClockView @JvmOverloads constructor( super.onMeasure(widthMeasureSpec, heightMeasureSpec) val animator = textAnimator if (animator == null) { - textAnimator = TextAnimator(layout) { invalidate() } + textAnimator = textAnimatorFactory(layout, ::invalidate) onTextAnimatorInitialized?.run() onTextAnimatorInitialized = null } else { @@ -219,9 +222,6 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateAppearOnLockscreen() { - if (isAnimationEnabled && textAnimator == null) { - return - } setTextStyle( weight = dozingWeight, textSize = -1f, diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 0e1e0cb2adcf..b444f4c1110b 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -137,6 +137,10 @@ open class ClockEventController @Inject constructor( override fun onThemeChanged() { updateFun.updateColors() } + + override fun onDensityOrFontScaleChanged() { + clock?.events?.onFontSettingChanged() + } } private val batteryCallback = object : BatteryStateChangeCallback { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 436b756ea0cb..8f5cbb76222f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -54,6 +54,8 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; @@ -127,6 +129,7 @@ public class AuthContainerView extends LinearLayout private final float mTranslationY; @ContainerState private int mContainerState = STATE_UNKNOWN; private final Set<Integer> mFailedModalities = new HashSet<Integer>(); + private final OnBackInvokedCallback mBackCallback = this::onBackInvoked; private final @Background DelayableExecutor mBackgroundExecutor; private int mOrientation; @@ -362,8 +365,7 @@ public class AuthContainerView extends LinearLayout return false; } if (event.getAction() == KeyEvent.ACTION_UP) { - sendEarlyUserCanceled(); - animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); + onBackInvoked(); } return true; }); @@ -373,6 +375,11 @@ public class AuthContainerView extends LinearLayout requestFocus(); } + private void onBackInvoked() { + sendEarlyUserCanceled(); + animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); + } + void sendEarlyUserCanceled() { mConfig.mCallback.onSystemEvent( BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL, getRequestId()); @@ -520,6 +527,11 @@ public class AuthContainerView extends LinearLayout .start(); }); } + OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher(); + if (dispatcher != null) { + dispatcher.registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, mBackCallback); + } } private Animator.AnimatorListener getJankListener(View v, String type, long timeout) { @@ -618,6 +630,10 @@ public class AuthContainerView extends LinearLayout @Override public void onDetachedFromWindow() { + OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher(); + if (dispatcher != null) { + findOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback); + } super.onDetachedFromWindow(); mWakefulnessLifecycle.removeObserver(this); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 163e3abd4cbc..5610377367c4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -57,6 +57,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.UserManager; +import android.util.DisplayUtils; import android.util.Log; import android.util.RotationUtils; import android.util.SparseBooleanArray; @@ -120,8 +121,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba private final Provider<UdfpsController> mUdfpsControllerFactory; private final Provider<SidefpsController> mSidefpsControllerFactory; - @NonNull private Point mStableDisplaySize = new Point(); - private final Display mDisplay; private float mScaleFactor = 1f; // sensor locations without any resolution scaling nor rotation adjustments: @@ -287,7 +286,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba } }); mUdfpsController.setAuthControllerUpdateUdfpsLocation(this::updateUdfpsLocation); - mUdfpsController.setHalControlsIllumination(mUdfpsProps.get(0).halControlsIllumination); mUdfpsBounds = mUdfpsProps.get(0).getLocation().getRect(); } @@ -532,10 +530,11 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba */ private void updateSensorLocations() { mDisplay.getDisplayInfo(mCachedDisplayInfo); - + final Display.Mode maxDisplayMode = + DisplayUtils.getMaximumResolutionDisplayMode(mCachedDisplayInfo.supportedModes); final float scaleFactor = android.util.DisplayUtils.getPhysicalPixelDisplaySizeRatio( - mStableDisplaySize.x, mStableDisplaySize.y, mCachedDisplayInfo.getNaturalWidth(), - mCachedDisplayInfo.getNaturalHeight()); + maxDisplayMode.getPhysicalWidth(), maxDisplayMode.getPhysicalHeight(), + mCachedDisplayInfo.getNaturalWidth(), mCachedDisplayInfo.getNaturalHeight()); if (scaleFactor == Float.POSITIVE_INFINITY) { mScaleFactor = 1f; } else { @@ -813,7 +812,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba ); } - mStableDisplaySize = mDisplayManager.getStableDisplaySize(); mActivityTaskManager.registerTaskStackListener(mTaskStackListener); mOrientationListener.enable(); updateSensorLocations(); @@ -1197,7 +1195,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { final AuthDialog dialog = mCurrentDialog; - pw.println(" stableDisplaySize=" + mStableDisplaySize); pw.println(" mCachedDisplayInfo=" + mCachedDisplayInfo); pw.println(" mScaleFactor=" + mScaleFactor); pw.println(" faceAuthSensorLocationDefault=" + mFaceSensorLocationDefault); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 35d96c912b01..31e1fb4e5d9b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -23,7 +23,6 @@ import android.content.Context import android.graphics.Point import android.hardware.biometrics.BiometricFingerprintConstants import android.hardware.biometrics.BiometricSourceType -import android.util.Log import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback @@ -95,7 +94,6 @@ class AuthRippleController @Inject constructor( public override fun onViewAttached() { authController.addCallback(authControllerCallback) updateRippleColor() - updateSensorLocation() updateUdfpsDependentParams() udfpsController?.addCallback(udfpsControllerCallback) configurationController.addCallback(configurationChangedListener) @@ -237,7 +235,11 @@ class AuthRippleController @Inject constructor( } private fun showDwellRipple() { - mView.startDwellRipple(statusBarStateController.isDozing) + updateSensorLocation() + fingerprintSensorLocation?.let { + mView.setFingerprintSensorLocation(it, udfpsRadius) + mView.startDwellRipple(statusBarStateController.isDozing) + } } private val keyguardUpdateMonitorCallback = @@ -264,7 +266,7 @@ class AuthRippleController @Inject constructor( acquireInfo: Int ) { if (biometricSourceType == BiometricSourceType.FINGERPRINT && - BiometricFingerprintConstants.shouldTurnOffHbm(acquireInfo) && + BiometricFingerprintConstants.shouldDisableUdfpsDisplayMode(acquireInfo) && acquireInfo != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD) { // received an 'acquiredBad' message, so immediately retract mView.retractDwellRipple() @@ -291,13 +293,6 @@ class AuthRippleController @Inject constructor( private val udfpsControllerCallback = object : UdfpsController.Callback { override fun onFingerDown() { - if (fingerprintSensorLocation == null) { - Log.e("AuthRipple", "fingerprintSensorLocation=null onFingerDown. " + - "Skip showing dwell ripple") - return - } - - mView.setFingerprintSensorLocation(fingerprintSensorLocation!!, udfpsRadius) showDwellRipple() } @@ -310,12 +305,10 @@ class AuthRippleController @Inject constructor( object : AuthController.Callback { override fun onAllAuthenticatorsRegistered(modality: Int) { updateUdfpsDependentParams() - updateSensorLocation() } override fun onUdfpsLocationChanged() { updateUdfpsDependentParams() - updateSensorLocation() } } @@ -345,13 +338,11 @@ class AuthRippleController @Inject constructor( "\n\tudfpsRadius=$udfpsRadius") } "fingerprint" -> { - updateSensorLocation() pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation") showUnlockRipple(BiometricSourceType.FINGERPRINT) } "face" -> { // note: only shows when about to proceed to the home screen - updateSensorLocation() pw.println("face ripple sensorLocation=$faceSensorLocation") showUnlockRipple(BiometricSourceType.FACE) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java index 9281eb8acb56..ad966125b9e8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java @@ -54,13 +54,13 @@ public abstract class UdfpsAnimationView extends FrameLayout { getDrawable().onSensorRectUpdated(bounds); } - void onIlluminationStarting() { - getDrawable().setIlluminationShowing(true); + void onDisplayConfiguring() { + getDrawable().setDisplayConfigured(true); getDrawable().invalidateSelf(); } - void onIlluminationStopped() { - getDrawable().setIlluminationShowing(false); + void onDisplayUnconfigured() { + getDrawable().setDisplayConfigured(false); getDrawable().invalidateSelf(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt index 742c65c2f854..3ad2bef97ac3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt @@ -31,10 +31,10 @@ import java.io.PrintWriter /** * Handles: * 1. registering for listeners when its view is attached and unregistering on view detached - * 2. pausing udfps when fingerprintManager may still be running but we temporarily want to hide + * 2. pausing UDFPS when FingerprintManager may still be running but we temporarily want to hide * the affordance. this allows us to fade the view in and out nicely (see shouldPauseAuth) * 3. sending events to its view including: - * - illumination events + * - enabling and disabling of the UDFPS display mode * - sensor position changes * - doze time event */ @@ -167,19 +167,20 @@ abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>( } /** - * Udfps has started illuminating and the fingerprint manager is working on authenticating. + * The display began transitioning into the UDFPS mode and the fingerprint manager started + * authenticating. */ - fun onIlluminationStarting() { - view.onIlluminationStarting() + fun onDisplayConfiguring() { + view.onDisplayConfiguring() view.postInvalidate() } /** - * Udfps has stopped illuminating and the fingerprint manager is no longer attempting to - * authenticate. + * The display transitioned away from the UDFPS mode and the fingerprint manager stopped + * authenticating. */ - fun onIlluminationStopped() { - view.onIlluminationStopped() + fun onDisplayUnconfigured() { + view.onDisplayUnconfigured() view.postInvalidate() } @@ -197,4 +198,4 @@ abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>( * Called when a view should announce an accessibility event. */ open fun doAnnounceForAccessibility(str: String) {} -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 7f2680b2137d..36287f59d746 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -86,7 +86,7 @@ import kotlin.Unit; /** * Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events, - * and coordinates triggering of the high-brightness mode (HBM). + * and toggles the UDFPS display mode. * * Note that the current architecture is designed so that a single {@link UdfpsController} * controls/manages all UDFPS sensors. In other words, a single controller is registered with @@ -123,7 +123,7 @@ public class UdfpsController implements DozeReceiver { @NonNull private final PowerManager mPowerManager; @NonNull private final AccessibilityManager mAccessibilityManager; @NonNull private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; - @Nullable private final UdfpsHbmProvider mHbmProvider; + @Nullable private final UdfpsDisplayModeProvider mUdfpsDisplayMode; @NonNull private final ConfigurationController mConfigurationController; @NonNull private final SystemClock mSystemClock; @NonNull private final UnlockedScreenOffAnimationController @@ -135,7 +135,6 @@ public class UdfpsController implements DozeReceiver { // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple // sensors, this, in addition to a lot of the code here, will be updated. @VisibleForTesting int mSensorId; - private boolean mHalControlsIllumination; @VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams(); // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this. @Nullable private Runnable mAuthControllerUpdateUdfpsLocation; @@ -147,10 +146,9 @@ public class UdfpsController implements DozeReceiver { private int mActivePointerId = -1; // The timestamp of the most recent touch log. private long mTouchLogTime; - // Sensor has a capture (good or bad) for this touch. Do not need to illuminate for this - // particular touch event anymore. In other words, do not illuminate until user lifts and - // touches the sensor area again. - // TODO: We should probably try to make touch/illumination things more of a FSM + // Sensor has a capture (good or bad) for this touch. No need to enable the UDFPS display mode + // anymore for this particular touch event. In other words, do not enable the UDFPS mode until + // the user touches the sensor area again. private boolean mAcquiredReceived; // The current request from FingerprintService. Null if no current request. @@ -211,8 +209,8 @@ public class UdfpsController implements DozeReceiver { mKeyguardUpdateMonitor, mDialogManager, mDumpManager, mLockscreenShadeTransitionController, mConfigurationController, mSystemClock, mKeyguardStateController, - mUnlockedScreenOffAnimationController, mHalControlsIllumination, - mHbmProvider, requestId, reason, callback, + mUnlockedScreenOffAnimationController, + mUdfpsDisplayMode, requestId, reason, callback, (view, event, fromUdfpsView) -> onTouch(requestId, event, fromUdfpsView), mActivityLaunchAnimator))); } @@ -236,7 +234,7 @@ public class UdfpsController implements DozeReceiver { int sensorId, @BiometricFingerprintConstants.FingerprintAcquired int acquiredInfo ) { - if (BiometricFingerprintConstants.shouldTurnOffHbm(acquiredInfo)) { + if (BiometricFingerprintConstants.shouldDisableUdfpsDisplayMode(acquiredInfo)) { boolean acquiredGood = acquiredInfo == FINGERPRINT_ACQUIRED_GOOD; mFgExecutor.execute(() -> { if (mOverlay == null) { @@ -247,7 +245,7 @@ public class UdfpsController implements DozeReceiver { mAcquiredReceived = true; final UdfpsView view = mOverlay.getOverlayView(); if (view != null) { - view.stopIllumination(); // turn off HBM + view.unconfigureDisplay(); } if (acquiredGood) { mOverlay.onAcquiredGood(); @@ -292,7 +290,7 @@ public class UdfpsController implements DozeReceiver { /** * Updates the overlay parameters and reconstructs or redraws the overlay, if necessary. * - * @param sensorId sensor for which the overlay is getting updated. + * @param sensorId sensor for which the overlay is getting updated. * @param overlayParams See {@link UdfpsOverlayParams}. */ public void updateOverlayParams(int sensorId, @NonNull UdfpsOverlayParams overlayParams) { @@ -321,11 +319,6 @@ public class UdfpsController implements DozeReceiver { mAuthControllerUpdateUdfpsLocation = r; } - // TODO(b/229290039): UDFPS controller should manage its properties on its own. Remove this. - public void setHalControlsIllumination(boolean value) { - mHalControlsIllumination = value; - } - /** * Calculate the pointer speed given a velocity tracker and the pointer id. * This assumes that the velocity tracker has already been passed all relevant motion events. @@ -369,8 +362,8 @@ public class UdfpsController implements DozeReceiver { } /** - * @param x coordinate - * @param y coordinate + * @param x coordinate + * @param y coordinate * @param relativeToUdfpsView true if the coordinates are relative to the udfps view; else, * calculate from the display dimensions in portrait orientation */ @@ -423,7 +416,7 @@ public class UdfpsController implements DozeReceiver { } final UdfpsView udfpsView = mOverlay.getOverlayView(); - final boolean isIlluminationRequested = udfpsView.isIlluminationRequested(); + final boolean isDisplayConfigured = udfpsView.isDisplayConfigured(); boolean handled = false; switch (event.getActionMasked()) { case MotionEvent.ACTION_OUTSIDE: @@ -507,7 +500,7 @@ public class UdfpsController implements DozeReceiver { "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b", minor, major, v, exceedsVelocityThreshold); final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime; - if (!isIlluminationRequested && !mAcquiredReceived + if (!isDisplayConfigured && !mAcquiredReceived && !exceedsVelocityThreshold) { final float scale = mOverlayParams.getScaleFactor(); @@ -598,7 +591,7 @@ public class UdfpsController implements DozeReceiver { @NonNull VibratorHelper vibrator, @NonNull UdfpsHapticsSimulator udfpsHapticsSimulator, @NonNull UdfpsShell udfpsShell, - @NonNull Optional<UdfpsHbmProvider> hbmProvider, + @NonNull Optional<UdfpsDisplayModeProvider> udfpsDisplayMode, @NonNull KeyguardStateController keyguardStateController, @NonNull DisplayManager displayManager, @Main Handler mainHandler, @@ -630,7 +623,7 @@ public class UdfpsController implements DozeReceiver { mPowerManager = powerManager; mAccessibilityManager = accessibilityManager; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; - mHbmProvider = hbmProvider.orElse(null); + mUdfpsDisplayMode = udfpsDisplayMode.orElse(null); screenLifecycle.addObserver(mScreenObserver); mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON; mConfigurationController = configurationController; @@ -804,15 +797,14 @@ public class UdfpsController implements DozeReceiver { } /** - * Cancel updfs scan affordances - ability to hide the HbmSurfaceView (white circle) before - * user explicitly lifts their finger. Generally, this should be called whenever udfps fails - * or errors. + * Cancel UDFPS affordances - ability to hide the UDFPS overlay before the user explicitly + * lifts their finger. Generally, this should be called on errors in the authentication flow. * * The sensor that triggers an AOD fingerprint interrupt (see onAodInterrupt) doesn't give * ACTION_UP/ACTION_CANCEL events, so and AOD interrupt scan needs to be cancelled manually. * This should be called when authentication either succeeds or fails. Failing to cancel the - * scan will leave the screen in high brightness mode and will show the HbmSurfaceView until - * the user lifts their finger. + * scan will leave the display in the UDFPS mode until the user lifts their finger. On optical + * sensors, this can result in illumination persisting for longer than necessary. */ void onCancelUdfps() { if (mOverlay != null && mOverlay.getOverlayView() != null) { @@ -874,7 +866,7 @@ public class UdfpsController implements DozeReceiver { Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0); final UdfpsView view = mOverlay.getOverlayView(); if (view != null) { - view.startIllumination(() -> { + view.configureDisplay(() -> { if (mAlternateTouchProvider != null) { mBiometricExecutor.execute(() -> { mAlternateTouchProvider.onUiReady(); @@ -914,8 +906,8 @@ public class UdfpsController implements DozeReceiver { } } mOnFingerDown = false; - if (view.isIlluminationRequested()) { - view.stopIllumination(); + if (view.isDisplayConfigured()) { + view.unconfigureDisplay(); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index ec720579fbee..1c62f8a4e508 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -77,8 +77,7 @@ class UdfpsControllerOverlay( private val systemClock: SystemClock, private val keyguardStateController: KeyguardStateController, private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, - private val halControlsIllumination: Boolean, - private var hbmProvider: UdfpsHbmProvider, + private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider, val requestId: Long, @ShowReason val requestReason: Int, private val controllerCallback: IUdfpsOverlayControllerCallback, @@ -102,8 +101,8 @@ class UdfpsControllerOverlay( fitInsetsTypes = 0 gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS - flags = - (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH) + flags = (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or + WindowManager.LayoutParams.FLAG_SPLIT_TOUCH) privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY // Avoid announcing window title. accessibilityTitle = " " @@ -140,8 +139,7 @@ class UdfpsControllerOverlay( R.layout.udfps_view, null, false ) as UdfpsView).apply { overlayParams = params - halControlsIllumination = this@UdfpsControllerOverlay.halControlsIllumination - setHbmProvider(hbmProvider) + setUdfpsDisplayModeProvider(udfpsDisplayModeProvider) val animation = inflateUdfpsAnimation(this, controller) if (animation != null) { animation.init() @@ -250,8 +248,8 @@ class UdfpsControllerOverlay( val wasShowing = isShowing overlayView?.apply { - if (isIlluminationRequested) { - stopIllumination() + if (isDisplayConfigured) { + unconfigureDisplay() } windowManager.removeView(this) setOnTouchListener(null) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayModeProvider.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayModeProvider.java new file mode 100644 index 000000000000..c6957acf1dc2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayModeProvider.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import android.annotation.Nullable; + +/** + * Interface for toggling the optimal display mode for the under-display fingerprint sensor + * (UDFPS). For example, the implementation might change the refresh rate and activate a + * high-brightness mode. + */ +public interface UdfpsDisplayModeProvider { + + /** + * Enables the optimal display mode for UDFPS. The mode will persist until + * {@link #disable(Runnable)} is called. + * + * This call must be made from the UI thread. The callback, if provided, will also be invoked + * from the UI thread. + * + * @param onEnabled A runnable that will be executed once the mode is enabled. + */ + void enable(@Nullable Runnable onEnabled); + + /** + * Disables the mode that was enabled by {@link #enable(Runnable)}. + * + * The call must be made from the UI thread. The callback, if provided, will also be invoked + * from the UI thread. + * + * @param onDisabled A runnable that will be executed once mode is disabled. + */ + void disable(@Nullable Runnable onDisabled); +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.kt index ee112b47e243..511b4e34fa0e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.kt @@ -51,7 +51,7 @@ abstract class UdfpsDrawable( invalidateSelf() } - var isIlluminationShowing: Boolean = false + var isDisplayConfigured: Boolean = false set(showing) { if (field == showing) { return diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java index 1317492aefac..1e359584ceec 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java @@ -197,7 +197,7 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { @Override public void draw(@NonNull Canvas canvas) { - if (isIlluminationShowing()) { + if (isDisplayConfigured()) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.kt index 1afa36bd5000..9f6b6d7472f9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.kt @@ -23,7 +23,7 @@ import android.graphics.Canvas */ class UdfpsFpDrawable(context: Context) : UdfpsDrawable(context) { override fun draw(canvas: Canvas) { - if (isIlluminationShowing) { + if (isDisplayConfigured) { return } fingerprintDrawable.draw(canvas) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java deleted file mode 100644 index f26dd5f57061..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics; - -import android.annotation.Nullable; - -/** - * Interface for controlling the high-brightness mode (HBM). UdfpsView can use this callback to - * enable the HBM while showing the fingerprint illumination, and to disable the HBM after the - * illumination is no longer necessary. - */ -public interface UdfpsHbmProvider { - - /** - * UdfpsView will call this to enable the HBM when the fingerprint illumination is needed. - * - * This method is a no-op when some type of HBM is already enabled. - * - * This method must be called from the UI thread. The callback, if provided, will also be - * invoked from the UI thread. - * - * @param onHbmEnabled A runnable that will be executed once HBM is enabled. - * - * TODO(b/231335067): enableHbm with halControlsIllumination=true shouldn't make sense. - * This only makes sense now because vendor code may rely on the side effects of enableHbm. - */ - void enableHbm(boolean halControlsIllumination, @Nullable Runnable onHbmEnabled); - - /** - * UdfpsView will call this to disable HBM when illumination is no longer needed. - * - * This method will disable HBM if HBM is enabled. Otherwise, if HBM is already disabled, - * this method is a no-op. - * - * The call must be made from the UI thread. The callback, if provided, will also be invoked - * from the UI thread. - * - * @param onHbmDisabled A runnable that will be executed once HBM is disabled. - */ - void disableHbm(@Nullable Runnable onHbmDisabled); -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java deleted file mode 100644 index f85e9361ecf2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics; - -import android.annotation.Nullable; - -/** - * Interface that should be implemented by UI's that need to coordinate user touches, - * views/animations, and modules that start/stop display illumination. - */ -interface UdfpsIlluminator { - /** - * @param hbmProvider Invoked when HBM should be enabled or disabled. - */ - void setHbmProvider(@Nullable UdfpsHbmProvider hbmProvider); - - /** - * Invoked when illumination should start. - * @param onIlluminatedRunnable Invoked when the display has been illuminated. - */ - void startIllumination(@Nullable Runnable onIlluminatedRunnable); - - /** - * Invoked when illumination should end. - */ - void stopIllumination(); -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java index f28fedb9155b..bc274a0af95f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java @@ -101,11 +101,11 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { } @Override - void onIlluminationStarting() { + void onDisplayConfiguring() { } @Override - void onIlluminationStopped() { + void onDisplayUnconfigured() { } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt index 245c2252d57b..a15456d46897 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt @@ -36,12 +36,12 @@ private const val TAG = "UdfpsView" class UdfpsView( context: Context, attrs: AttributeSet? -) : FrameLayout(context, attrs), DozeReceiver, UdfpsIlluminator { +) : FrameLayout(context, attrs), DozeReceiver { // sensorRect may be bigger than the sensor. True sensor dimensions are defined in // overlayParams.sensorBounds private val sensorRect = RectF() - private var hbmProvider: UdfpsHbmProvider? = null + private var mUdfpsDisplayMode: UdfpsDisplayModeProvider? = null private val debugTextPaint = Paint().apply { isAntiAlias = true color = Color.BLUE @@ -56,19 +56,12 @@ class UdfpsView( a.getFloat(R.styleable.UdfpsView_sensorTouchAreaCoefficient, 0f) } - private val onIlluminatedDelayMs = context.resources.getInteger( - com.android.internal.R.integer.config_udfps_illumination_transition_ms - ).toLong() - /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */ var animationViewController: UdfpsAnimationViewController<*>? = null /** Parameters that affect the position and size of the overlay. */ var overlayParams = UdfpsOverlayParams() - /** Whether the HAL is responsible for enabling and disabling of LHBM. */ - var halControlsIllumination: Boolean = true - /** Debug message. */ var debugMessage: String? = null set(value) { @@ -76,12 +69,12 @@ class UdfpsView( postInvalidate() } - /** When [startIllumination] has been called but not stopped via [stopIllumination]. */ - var isIlluminationRequested: Boolean = false + /** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */ + var isDisplayConfigured: Boolean = false private set - override fun setHbmProvider(provider: UdfpsHbmProvider?) { - hbmProvider = provider + fun setUdfpsDisplayModeProvider(udfpsDisplayModeProvider: UdfpsDisplayModeProvider?) { + mUdfpsDisplayMode = udfpsDisplayModeProvider } // Don't propagate any touch events to the child views. @@ -124,7 +117,7 @@ class UdfpsView( override fun onDraw(canvas: Canvas) { super.onDraw(canvas) - if (!isIlluminationRequested) { + if (!isDisplayConfigured) { if (!debugMessage.isNullOrEmpty()) { canvas.drawText(debugMessage!!, 0f, 160f, debugTextPaint) } @@ -147,36 +140,15 @@ class UdfpsView( !(animationViewController?.shouldPauseAuth() ?: false) } - /** - * Start and run [onIlluminatedRunnable] when the first illumination frame reaches the panel. - */ - override fun startIllumination(onIlluminatedRunnable: Runnable?) { - isIlluminationRequested = true - animationViewController?.onIlluminationStarting() - doIlluminate(onIlluminatedRunnable) - } - - private fun doIlluminate(onIlluminatedRunnable: Runnable?) { - // TODO(b/231335067): enableHbm with halControlsIllumination=true shouldn't make sense. - // This only makes sense now because vendor code may rely on the side effects of enableHbm. - hbmProvider?.enableHbm(halControlsIllumination) { - if (onIlluminatedRunnable != null) { - if (halControlsIllumination) { - onIlluminatedRunnable.run() - } else { - // No framework API can reliably tell when a frame reaches the panel. A timeout - // is the safest solution. - postDelayed(onIlluminatedRunnable, onIlluminatedDelayMs) - } - } else { - Log.w(TAG, "doIlluminate | onIlluminatedRunnable is null") - } - } + fun configureDisplay(onDisplayConfigured: Runnable) { + isDisplayConfigured = true + animationViewController?.onDisplayConfiguring() + mUdfpsDisplayMode?.enable(onDisplayConfigured) } - override fun stopIllumination() { - isIlluminationRequested = false - animationViewController?.onIlluminationStopped() - hbmProvider?.disableHbm(null /* onHbmDisabled */) + fun unconfigureDisplay() { + isDisplayConfigured = false + animationViewController?.onDisplayUnconfigured() + mUdfpsDisplayMode?.disable(null /* onDisabled */) } } diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt new file mode 100644 index 000000000000..96af42bfda22 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bluetooth + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.BluetoothLog +import javax.inject.Inject + +/** Helper class for logging bluetooth events. */ +@SysUISingleton +class BluetoothLogger @Inject constructor(@BluetoothLog private val logBuffer: LogBuffer) { + fun logActiveDeviceChanged(address: String?, profileId: Int) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + int1 = profileId + }, + { "ActiveDeviceChanged. address=$str1 profileId=$int1" } + ) + + fun logDeviceConnectionStateChanged(address: String?, state: String) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + str2 = state + }, + { "DeviceConnectionStateChanged. address=$str1 state=$str2" } + ) + + fun logAclConnectionStateChanged(address: String, state: String) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + str2 = state + }, + { "AclConnectionStateChanged. address=$str1 state=$str2" } + ) + + fun logProfileConnectionStateChanged(address: String?, state: String, profileId: Int) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + str2 = state + int1 = profileId + }, + { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" } + ) + + fun logStateChange(state: String) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { str1 = state }, + { "BluetoothStateChanged. state=$str1" } + ) + + fun logBondStateChange(address: String, state: Int) = + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = address + int1 = state + }, + { "DeviceBondStateChanged. address=$str1 state=$int1" } + ) + + fun logDeviceAdded(address: String) = + logBuffer.log(TAG, LogLevel.DEBUG, { str1 = address }, { "DeviceAdded. address=$str1" }) + + fun logDeviceDeleted(address: String) = + logBuffer.log(TAG, LogLevel.DEBUG, { str1 = address }, { "DeviceDeleted. address=$str1" }) + + fun logDeviceAttributesChanged() = + logBuffer.log(TAG, LogLevel.DEBUG, {}, { "DeviceAttributesChanged." }) +} + +private const val TAG = "BluetoothLog" diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index 31a2134851a2..bfbf37a5d3e9 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -291,7 +291,7 @@ public class BrightLineFalsingManager implements FalsingManager { FalsingClassifier.Result.falsed( 0, getClass().getSimpleName(), "bad history")); logDebug("False Single Tap: true (bad history)"); - mFalsingTapListeners.forEach(FalsingTapListener::onDoubleTapRequired); + mFalsingTapListeners.forEach(FalsingTapListener::onAdditionalTapRequired); return true; } else { mPriorResults = getPassedResult(0.1); @@ -321,7 +321,7 @@ public class BrightLineFalsingManager implements FalsingManager { mHistoryTracker.falseBelief(), mHistoryTracker.falseConfidence()); mPriorResults = Collections.singleton(result); - logDebug("False Double Tap: " + result.isFalse()); + logDebug("False Double Tap: " + result.isFalse() + " reason=" + result.getReason()); return result.isFalse(); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java index 23d87ff980ca..f5f9655ef24b 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java @@ -303,9 +303,7 @@ class FalsingCollectorImpl implements FalsingCollector { @Override public void onTouchEvent(MotionEvent ev) { - if (!mKeyguardStateController.isShowing() - || (mStatusBarStateController.isDozing() - && !mStatusBarStateController.isPulsing())) { + if (!mKeyguardStateController.isShowing()) { avoidGesture(); return; } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index c21e36ab6ecc..7e499ebdf691 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -119,15 +119,12 @@ import java.util.ArrayList; */ public class ClipboardOverlayController { private static final String TAG = "ClipboardOverlayCtrlr"; - private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY"; /** Constants for screenshot/copy deconflicting */ public static final String SCREENSHOT_ACTION = "com.android.systemui.SCREENSHOT"; public static final String SELF_PERMISSION = "com.android.systemui.permission.SELF"; public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY"; - private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard"; - private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000; private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe private static final int FONT_SEARCH_STEP_PX = 4; @@ -383,7 +380,7 @@ public class ClipboardOverlayController { mTextPreview); accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied); } - Intent remoteCopyIntent = getRemoteCopyIntent(clipData); + Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext); // Only show remote copy if it's available. PackageManager packageManager = mContext.getPackageManager(); if (packageManager.resolveActivity( @@ -500,41 +497,19 @@ public class ClipboardOverlayController { private void editImage(Uri uri) { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED); - String editorPackage = mContext.getString(R.string.config_screenshotEditor); - Intent editIntent = new Intent(Intent.ACTION_EDIT); - if (!TextUtils.isEmpty(editorPackage)) { - editIntent.setComponent(ComponentName.unflattenFromString(editorPackage)); - } - editIntent.setDataAndType(uri, "image/*"); - editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - editIntent.putExtra(EXTRA_EDIT_SOURCE_CLIPBOARD, true); - mContext.startActivity(editIntent); + mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext)); animateOut(); } private void editText() { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED); - Intent editIntent = new Intent(mContext, EditTextActivity.class); - editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - mContext.startActivity(editIntent); + mContext.startActivity(IntentCreator.getTextEditorIntent(mContext)); animateOut(); } private void shareContent(ClipData clip) { mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED); - Intent shareIntent = new Intent(Intent.ACTION_SEND); - shareIntent.setDataAndType( - clip.getItemAt(0).getUri(), clip.getDescription().getMimeType(0)); - shareIntent.putExtra(Intent.EXTRA_TEXT, clip.getItemAt(0).getText().toString()); - if (clip.getItemAt(0).getUri() != null) { - shareIntent.putExtra(Intent.EXTRA_STREAM, clip.getItemAt(0).getUri()); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - Intent chooserIntent = Intent.createChooser(shareIntent, null) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - mContext.startActivity(chooserIntent); + mContext.startActivity(IntentCreator.getShareIntent(clip, mContext)); animateOut(); } @@ -667,20 +642,6 @@ public class ClipboardOverlayController { mContext.getString(R.string.clipboard_edit), null); } - private Intent getRemoteCopyIntent(ClipData clipData) { - Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION); - - String remoteCopyPackage = mContext.getString(R.string.config_remoteCopyPackage); - if (!TextUtils.isEmpty(remoteCopyPackage)) { - nearbyIntent.setComponent(ComponentName.unflattenFromString(remoteCopyPackage)); - } - - nearbyIntent.setClipData(clipData); - nearbyIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - return nearbyIntent; - } - private void animateIn() { if (mAccessibilityManager.isEnabled()) { mDismissButton.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java new file mode 100644 index 000000000000..3d5e601f18f5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.clipboardoverlay; + +import android.content.ClipData; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.text.TextUtils; + +import com.android.systemui.R; + +class IntentCreator { + private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard"; + private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY"; + + static Intent getTextEditorIntent(Context context) { + Intent intent = new Intent(context, EditTextActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return intent; + } + + static Intent getShareIntent(ClipData clipData, Context context) { + Intent shareIntent = new Intent(Intent.ACTION_SEND); + + // From the ACTION_SEND docs: + // "If using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it should be the + // MIME type of the data in EXTRA_STREAM" + if (clipData.getItemAt(0).getUri() != null) { + shareIntent.setDataAndType( + clipData.getItemAt(0).getUri(), clipData.getDescription().getMimeType(0)); + shareIntent.putExtra(Intent.EXTRA_STREAM, clipData.getItemAt(0).getUri()); + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } else { + shareIntent.putExtra(Intent.EXTRA_TEXT, clipData.getItemAt(0).coerceToText(context)); + shareIntent.setType("text/plain"); + } + Intent chooserIntent = Intent.createChooser(shareIntent, null) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + return chooserIntent; + } + + static Intent getImageEditIntent(Uri uri, Context context) { + String editorPackage = context.getString(R.string.config_screenshotEditor); + Intent editIntent = new Intent(Intent.ACTION_EDIT); + if (!TextUtils.isEmpty(editorPackage)) { + editIntent.setComponent(ComponentName.unflattenFromString(editorPackage)); + } + editIntent.setDataAndType(uri, "image/*"); + editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + editIntent.putExtra(EXTRA_EDIT_SOURCE_CLIPBOARD, true); + return editIntent; + } + + static Intent getRemoteCopyIntent(ClipData clipData, Context context) { + Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION); + + String remoteCopyPackage = context.getString(R.string.config_remoteCopyPackage); + if (!TextUtils.isEmpty(remoteCopyPackage)) { + nearbyIntent.setComponent(ComponentName.unflattenFromString(remoteCopyPackage)); + } + + nearbyIntent.setClipData(clipData); + nearbyIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return nearbyIntent; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 318529b289ec..0469152de776 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -30,7 +30,7 @@ import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.appops.dagger.AppOpsModule; import com.android.systemui.assist.AssistModule; import com.android.systemui.biometrics.AlternateUdfpsTouchProvider; -import com.android.systemui.biometrics.UdfpsHbmProvider; +import com.android.systemui.biometrics.UdfpsDisplayModeProvider; import com.android.systemui.biometrics.dagger.BiometricsModule; import com.android.systemui.classifier.FalsingModule; import com.android.systemui.controls.dagger.ControlsModule; @@ -197,7 +197,7 @@ public abstract class SystemUIModule { abstract CentralSurfaces optionalCentralSurfaces(); @BindsOptionalOf - abstract UdfpsHbmProvider optionalUdfpsHbmProvider(); + abstract UdfpsDisplayModeProvider optionalUdfpsDisplayModeProvider(); @BindsOptionalOf abstract AlternateUdfpsTouchProvider optionalUdfpsTouchProvider(); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java index 653f4dc66200..789ebc517271 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java @@ -17,24 +17,21 @@ package com.android.systemui.dreams.complication; import android.content.Context; +import android.content.res.Resources; import android.graphics.Canvas; import android.util.AttributeSet; import android.widget.TextClock; import com.android.systemui.R; +import com.android.systemui.dreams.complication.DoubleShadowTextHelper.ShadowInfo; + +import kotlin.Unit; /** * Extension of {@link TextClock} which draws two shadows on the text (ambient and key shadows) */ public class DoubleShadowTextClock extends TextClock { - private final float mAmbientShadowBlur; - private final int mAmbientShadowColor; - private final float mKeyShadowBlur; - private final float mKeyShadowOffsetX; - private final float mKeyShadowOffsetY; - private final int mKeyShadowColor; - private final float mAmbientShadowOffsetX; - private final float mAmbientShadowOffsetY; + private final DoubleShadowTextHelper mShadowHelper; public DoubleShadowTextClock(Context context) { this(context, null); @@ -46,38 +43,28 @@ public class DoubleShadowTextClock extends TextClock { public DoubleShadowTextClock(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mKeyShadowBlur = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_radius); - mKeyShadowOffsetX = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dx); - mKeyShadowOffsetY = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dy); - mKeyShadowColor = context.getResources().getColor( - R.color.dream_overlay_clock_key_text_shadow_color); - mAmbientShadowBlur = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_radius); - mAmbientShadowColor = context.getResources().getColor( - R.color.dream_overlay_clock_ambient_text_shadow_color); - mAmbientShadowOffsetX = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dx); - mAmbientShadowOffsetY = context.getResources() - .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dy); + + final Resources resources = context.getResources(); + final ShadowInfo keyShadowInfo = new ShadowInfo( + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_radius), + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dx), + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dy), + resources.getColor(R.color.dream_overlay_clock_key_text_shadow_color)); + + final ShadowInfo ambientShadowInfo = new ShadowInfo( + resources.getDimensionPixelSize( + R.dimen.dream_overlay_clock_ambient_text_shadow_radius), + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dx), + resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dy), + resources.getColor(R.color.dream_overlay_clock_ambient_text_shadow_color)); + mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo); } @Override public void onDraw(Canvas canvas) { - // We enhance the shadow by drawing the shadow twice - getPaint().setShadowLayer(mAmbientShadowBlur, mAmbientShadowOffsetX, mAmbientShadowOffsetY, - mAmbientShadowColor); - super.onDraw(canvas); - canvas.save(); - canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(), - getScrollX() + getWidth(), - getScrollY() + getHeight()); - - getPaint().setShadowLayer( - mKeyShadowBlur, mKeyShadowOffsetX, mKeyShadowOffsetY, mKeyShadowColor); - super.onDraw(canvas); - canvas.restore(); + mShadowHelper.applyShadows(this, canvas, () -> { + super.onDraw(canvas); + return Unit.INSTANCE; + }); } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt new file mode 100644 index 000000000000..b1dc5a2e5dea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication + +import android.graphics.Canvas +import android.widget.TextView +import androidx.annotation.ColorInt + +class DoubleShadowTextHelper +constructor( + private val keyShadowInfo: ShadowInfo, + private val ambientShadowInfo: ShadowInfo, +) { + data class ShadowInfo( + val blur: Float, + val offsetX: Float = 0f, + val offsetY: Float = 0f, + @ColorInt val color: Int + ) + + fun applyShadows(view: TextView, canvas: Canvas, onDrawCallback: () -> Unit) { + // We enhance the shadow by drawing the shadow twice + view.paint.setShadowLayer( + ambientShadowInfo.blur, + ambientShadowInfo.offsetX, + ambientShadowInfo.offsetY, + ambientShadowInfo.color + ) + onDrawCallback() + canvas.save() + canvas.clipRect( + view.scrollX, + view.scrollY + view.extendedPaddingTop, + view.scrollX + view.width, + view.scrollY + view.height + ) + + view.paint.setShadowLayer( + keyShadowInfo.blur, + keyShadowInfo.offsetX, + keyShadowInfo.offsetY, + keyShadowInfo.color + ) + onDrawCallback() + canvas.restore() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java new file mode 100644 index 000000000000..cf7e3127dedf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.widget.TextView; + +import com.android.systemui.R; + +import kotlin.Unit; + +/** + * Extension of {@link TextView} which draws two shadows on the text (ambient and key shadows} + */ +public class DoubleShadowTextView extends TextView { + private final DoubleShadowTextHelper mShadowHelper; + + public DoubleShadowTextView(Context context) { + this(context, null); + } + + public DoubleShadowTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DoubleShadowTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + final Resources resources = context.getResources(); + final DoubleShadowTextHelper.ShadowInfo + keyShadowInfo = new DoubleShadowTextHelper.ShadowInfo( + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_key_text_shadow_radius), + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_key_text_shadow_dx), + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_key_text_shadow_dy), + resources.getColor(R.color.dream_overlay_status_bar_key_text_shadow_color)); + + final DoubleShadowTextHelper.ShadowInfo + ambientShadowInfo = new DoubleShadowTextHelper.ShadowInfo( + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_ambient_text_shadow_radius), + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_ambient_text_shadow_dx), + resources.getDimensionPixelSize( + R.dimen.dream_overlay_status_bar_ambient_text_shadow_dy), + resources.getColor(R.color.dream_overlay_status_bar_ambient_text_shadow_color)); + mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo); + } + + @Override + public void onDraw(Canvas canvas) { + mShadowHelper.applyShadows(this, canvas, () -> { + super.onDraw(canvas); + return Unit.INSTANCE; + }); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java index a3dc77993d30..568143c8df71 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java @@ -42,6 +42,8 @@ import android.util.Pair; import android.util.Slog; import android.widget.Toast; +import androidx.annotation.NonNull; + import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -601,13 +603,14 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo private final class BluetoothCallbackHandler implements BluetoothCallback { @Override - public void onBluetoothStateChanged(int bluetoothState) { + public void onBluetoothStateChanged(@BluetoothCallback.AdapterState int bluetoothState) { mHandler.obtainMessage(MSG_ON_BLUETOOTH_STATE_CHANGED, bluetoothState, 0).sendToTarget(); } @Override - public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { + public void onDeviceBondStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int bondState) { mHandler.obtainMessage(MSG_ON_DEVICE_BOND_STATE_CHANGED, bondState, 0, cachedDevice).sendToTarget(); } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt new file mode 100644 index 000000000000..4887b6a14658 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BluetoothLog.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger + +import javax.inject.Qualifier + +/** A [com.android.systemui.log.LogBuffer] for bluetooth. */ +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class BluetoothLog diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt index 323ee21953ea..b551125fccc7 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt @@ -1,4 +1,9 @@ package com.android.systemui.log.dagger +import javax.inject.Qualifier + /** A [com.android.systemui.log.LogBuffer] for KeyguardUpdateMonitor. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) annotation class KeyguardUpdateMonitorLog diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 756feb0c55c2..5612c22311fb 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -316,4 +316,14 @@ public class LogModule { public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) { return factory.create("KeyguardUpdateMonitorLog", 200); } + + /** + * Provides a {@link LogBuffer} for bluetooth-related logs. + */ + @Provides + @SysUISingleton + @BluetoothLog + public static LogBuffer providerBluetoothLogBuffer(LogBufferFactory factory) { + return factory.create("BluetoothLog", 50); + } } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java index f03fbcba41b7..b237f2d74483 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java @@ -19,7 +19,6 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.android.systemui.log.LogBuffer; -import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -27,7 +26,7 @@ import java.lang.annotation.Retention; import javax.inject.Qualifier; /** - * A {@link LogBuffer} for events processed by {@link ConnectivityInfoProcessor} + * A {@link LogBuffer} for status bar connectivity events. */ @Qualifier @Documented diff --git a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt index d0826553ad2c..556560c3534c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt @@ -34,7 +34,7 @@ import com.android.systemui.monet.ColorScheme * is triggered. */ interface ColorTransition { - fun updateColorScheme(scheme: ColorScheme?) + fun updateColorScheme(scheme: ColorScheme?): Boolean } /** @@ -64,14 +64,16 @@ open class AnimatingColorTransition( applyColor(currentColor) } - override fun updateColorScheme(scheme: ColorScheme?) { + override fun updateColorScheme(scheme: ColorScheme?): Boolean { val newTargetColor = if (scheme == null) defaultColor else extractColor(scheme) if (newTargetColor != targetColor) { sourceColor = currentColor targetColor = newTargetColor valueAnimator.cancel() valueAnimator.start() + return true } + return false } init { @@ -198,8 +200,10 @@ class ColorSchemeTransition internal constructor( return Utils.getColorAttr(context, id).defaultColor } - fun updateColorScheme(colorScheme: ColorScheme?) { - colorTransitions.forEach { it.updateColorScheme(colorScheme) } + fun updateColorScheme(colorScheme: ColorScheme?): Boolean { + var anyChanged = false + colorTransitions.forEach { anyChanged = it.updateColorScheme(colorScheme) || anyChanged } colorScheme?.let { mediaViewHolder.gutsViewHolder.colorScheme = colorScheme } + return anyChanged } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index cc77ed15b5f0..654c15812988 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -458,7 +458,7 @@ class MediaCarouselController @Inject constructor( val existingPlayer = MediaPlayerData.getMediaPlayer(key) val curVisibleMediaKey = MediaPlayerData.playerKeys() .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) - val isCurVisibleMediaPlaying = MediaPlayerData.getMediaData(curVisibleMediaKey)?.isPlaying + val isCurVisibleMediaPlaying = curVisibleMediaKey?.data?.isPlaying if (existingPlayer == null) { val newPlayer = mediaControlPanelFactory.get() newPlayer.attachPlayer(MediaViewHolder.create( @@ -1046,15 +1046,6 @@ internal object MediaPlayerData { } } - fun getMediaData(mediaSortKey: MediaSortKey?): MediaData? { - mediaData.forEach { (key, value) -> - if (value == mediaSortKey) { - return mediaData[key]?.data - } - } - return null - } - fun getMediaPlayer(key: String): MediaControlPanel? { return mediaData.get(key)?.let { mediaPlayers.get(it) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index b02393b4f73a..759795f84963 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -741,10 +741,14 @@ public class MediaControlPanel { } mArtworkBoundId = reqId; + // Transition Colors to current color scheme + boolean colorSchemeChanged = mColorSchemeTransition.updateColorScheme(colorScheme); + // Bind the album view to the artwork or a transition drawable ImageView albumView = mMediaViewHolder.getAlbumView(); albumView.setPadding(0, 0, 0, 0); - if (updateBackground || (!mIsArtworkBound && isArtworkBound)) { + if (updateBackground || colorSchemeChanged + || (!mIsArtworkBound && isArtworkBound)) { if (mPrevArtwork == null) { albumView.setImageDrawable(artwork); } else { @@ -767,9 +771,6 @@ public class MediaControlPanel { mIsArtworkBound = isArtworkBound; } - // Transition Colors to current color scheme - mColorSchemeTransition.updateColorScheme(colorScheme); - // App icon - use notification icon ImageView appIconView = mMediaViewHolder.getAppIcon(); appIconView.clearColorFilter(); diff --git a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt index 88f6f3dd9d0e..6bc94cd5f525 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt @@ -60,6 +60,8 @@ class SquigglyProgress : Drawable() { linePaint.strokeWidth = value } + // Enables a transition region where the amplitude + // of the wave is reduced linearly across it. var transitionEnabled = true set(value) { field = value @@ -116,44 +118,40 @@ class SquigglyProgress : Drawable() { } val progress = level / 10_000f - val totalProgressPx = bounds.width() * progress - val waveProgressPx = bounds.width() * ( + val totalWidth = bounds.width().toFloat() + val totalProgressPx = totalWidth * progress + val waveProgressPx = totalWidth * ( if (!transitionEnabled || progress > matchedWaveEndpoint) progress else lerp(minWaveEndpoint, matchedWaveEndpoint, lerpInv(0f, matchedWaveEndpoint, progress))) // Build Wiggly Path - val waveStart = -phaseOffset - val waveEnd = waveProgressPx - val transitionLength = if (transitionEnabled) transitionPeriods * waveLength else 0.01f + val waveStart = -phaseOffset - waveLength / 2f + val waveEnd = if (transitionEnabled) totalWidth else waveProgressPx // helper function, computes amplitude for wave segment val computeAmplitude: (Float, Float) -> Float = { x, sign -> - sign * heightFraction * lineAmplitude * - lerpInvSat(waveEnd, waveEnd - transitionLength, x) + if (transitionEnabled) { + val length = transitionPeriods * waveLength + val coeff = lerpInvSat( + waveProgressPx + length / 2f, + waveProgressPx - length / 2f, + x) + sign * heightFraction * lineAmplitude * coeff + } else { + sign * heightFraction * lineAmplitude + } } - var currentX = waveEnd - var waveSign = if (phaseOffset < waveLength / 2) 1f else -1f + // Reset path object to the start path.rewind() + path.moveTo(waveStart, 0f) - // Draw flat line from end to wave endpoint - path.moveTo(bounds.width().toFloat(), 0f) - path.lineTo(waveEnd, 0f) - - // First wave has shortened wavelength - // approx quarter wave gets us to first wave peak - // shouldn't be big enough to notice it's not a sin wave - currentX -= phaseOffset % (waveLength / 2) - val controlRatio = 0.25f + // Build the wave, incrementing by half the wavelength each time + var currentX = waveStart + var waveSign = 1f var currentAmp = computeAmplitude(currentX, waveSign) - path.cubicTo( - waveEnd, currentAmp * controlRatio, - lerp(currentX, waveEnd, controlRatio), currentAmp, - currentX, currentAmp) - - // Other waves have full wavelength - val dist = -1 * waveLength / 2f - while (currentX > waveStart) { + val dist = waveLength / 2f + while (currentX < waveEnd) { waveSign = -waveSign val nextX = currentX + dist val midX = currentX + dist / 2 @@ -166,34 +164,35 @@ class SquigglyProgress : Drawable() { currentX = nextX } - // Draw path; clip to progress position + // translate to the start position of the progress bar for all draw commands + val clipTop = lineAmplitude + strokeWidth canvas.save() canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat()) - canvas.clipRect( - 0f, - -lineAmplitude - strokeWidth, - totalProgressPx, - lineAmplitude + strokeWidth) - canvas.drawPath(path, wavePaint) - canvas.restore() - // Draw path; clip between progression position & far edge + // Draw path up to progress position canvas.save() - canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat()) - canvas.clipRect( - totalProgressPx, - -lineAmplitude - strokeWidth, - bounds.width().toFloat(), - lineAmplitude + strokeWidth) - canvas.drawPath(path, linePaint) + canvas.clipRect(0f, -1f * clipTop, totalProgressPx, clipTop) + canvas.drawPath(path, wavePaint) canvas.restore() + if (transitionEnabled) { + // If there's a smooth transition, we draw the rest of the + // path in a different color (using different clip params) + canvas.save() + canvas.clipRect(totalProgressPx, -1f * clipTop, totalWidth, clipTop) + canvas.drawPath(path, linePaint) + canvas.restore() + } else { + // No transition, just draw a flat line to the end of the region. + // The discontinuity is hidden by the progress bar thumb shape. + canvas.drawLine(totalProgressPx, 0f, totalWidth, 0f, linePaint) + } + // Draw round line cap at the beginning of the wave - val startAmp = cos(abs(waveEnd - phaseOffset) / waveLength * TWO_PI) - canvas.drawPoint( - bounds.left.toFloat(), - bounds.centerY() + startAmp * lineAmplitude * heightFraction, - wavePaint) + val startAmp = cos(abs(waveStart) / waveLength * TWO_PI) + canvas.drawPoint(0f, startAmp * lineAmplitude * heightFraction, wavePaint) + + canvas.restore() } override fun getOpacity(): Int { @@ -233,4 +232,4 @@ class SquigglyProgress : Drawable() { linePaint.color = ColorUtils.setAlphaComponent(tintColor, (DISABLED_ALPHA * (alpha / 255f)).toInt()) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 57bea67b7678..6e4c858e89df 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -24,6 +24,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.res.Configuration; import android.metrics.LogMaker; +import android.util.Log; import android.view.View; import com.android.internal.annotations.VisibleForTesting; @@ -61,6 +62,7 @@ import kotlin.jvm.functions.Function1; */ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewController<T> implements Dumpable{ + private static final String TAG = "QSPanelControllerBase"; protected final QSTileHost mHost; private final QSCustomizerController mQsCustomizerController; private final boolean mUsingMediaPlayer; @@ -90,6 +92,13 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr public void onConfigurationChange(Configuration newConfig) { mShouldUseSplitNotificationShade = LargeScreenUtils.shouldUseSplitNotificationShade(getResources()); + // Logging to aid the investigation of b/216244185. + Log.d(TAG, + "onConfigurationChange: " + + "mShouldUseSplitNotificationShade=" + + mShouldUseSplitNotificationShade + ", " + + "newConfig.windowConfiguration=" + + newConfig.windowConfiguration); mQSLogger.logOnConfigurationChanged(mLastOrientation, newConfig.orientation, mView.getDumpableTag()); onConfigurationChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java index c2a82a76d3e8..a31500c6bb18 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java @@ -49,7 +49,6 @@ import javax.inject.Inject; /** Quick settings tile: Invert colors **/ public class ColorInversionTile extends QSTileImpl<BooleanState> { - private final Icon mIcon = ResourceIcon.get(drawable.ic_invert_colors); private final SettingObserver mSetting; @Inject @@ -123,7 +122,9 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { state.value = enabled; state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.label = mContext.getString(R.string.quick_settings_inversion_label); - state.icon = mIcon; + state.icon = ResourceIcon.get(state.value + ? drawable.qs_invert_colors_icon_on + : drawable.qs_invert_colors_icon_off); state.expandedAccessibilityClassName = Switch.class.getName(); state.contentDescription = state.label; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java index 9fdf5940c392..2fc99f323611 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java @@ -128,13 +128,13 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements @Override protected void handleUpdateState(BooleanState state, Object arg) { - state.value = arg instanceof Boolean ? (Boolean) arg + state.value = arg instanceof Boolean ? ((Boolean) arg).booleanValue() : mDataSaverController.isDataSaverEnabled(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.label = mContext.getString(R.string.data_saver); state.contentDescription = state.label; - state.icon = ResourceIcon.get(state.value ? R.drawable.ic_data_saver - : R.drawable.ic_data_saver_off); + state.icon = ResourceIcon.get(state.value ? R.drawable.qs_data_saver_icon_on + : R.drawable.qs_data_saver_icon_off); state.expandedAccessibilityClassName = Switch.class.getName(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index 73b0896b06a5..a74792687289 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -43,11 +43,12 @@ import com.android.systemui.statusbar.policy.FlashlightController; import javax.inject.Inject; -/** Quick settings tile: Control flashlight **/ +/** + * Quick settings tile: Control flashlight + **/ public class FlashlightTile extends QSTileImpl<BooleanState> implements FlashlightController.FlashlightListener { - private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_flashlight); private final FlashlightController mFlashlightController; @Inject @@ -116,19 +117,15 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements @Override protected void handleUpdateState(BooleanState state, Object arg) { - if (state.slash == null) { - state.slash = new SlashState(); - } state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label); state.secondaryLabel = ""; state.stateDescription = ""; if (!mFlashlightController.isAvailable()) { - state.icon = mIcon; - state.slash.isSlashed = true; state.secondaryLabel = mContext.getString( R.string.quick_settings_flashlight_camera_in_use); state.stateDescription = state.secondaryLabel; state.state = Tile.STATE_UNAVAILABLE; + state.icon = ResourceIcon.get(R.drawable.qs_flashlight_icon_off); return; } if (arg instanceof Boolean) { @@ -140,11 +137,11 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements } else { state.value = mFlashlightController.isEnabled(); } - state.icon = mIcon; - state.slash.isSlashed = !state.value; state.contentDescription = mContext.getString(R.string.quick_settings_flashlight_label); state.expandedAccessibilityClassName = Switch.class.getName(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + state.icon = ResourceIcon.get(state.value + ? R.drawable.qs_flashlight_icon_on : R.drawable.qs_flashlight_icon_off); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index 81813db5abfd..0e9f6599522f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -144,9 +144,10 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements protected void handleUpdateState(BooleanState state, Object arg) { state.value = mManager.isNightDisplayActivated(); state.label = mContext.getString(R.string.quick_settings_night_display_label); - state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_night_display_on); state.expandedAccessibilityClassName = Switch.class.getName(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + state.icon = ResourceIcon.get(state.value ? R.drawable.qs_nightlight_icon_on + : R.drawable.qs_nightlight_icon_off); state.secondaryLabel = getSecondaryLabel(state.value); state.contentDescription = TextUtils.isEmpty(state.secondaryLabel) ? state.label diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java index 6921ad549626..1dac33909ba7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java @@ -49,7 +49,6 @@ import javax.inject.Named; public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> implements ReduceBrightColorsController.Listener{ - private final Icon mIcon = ResourceIcon.get(drawable.ic_reduce_bright_colors); private final boolean mIsAvailable; private final ReduceBrightColorsController mReduceBrightColorsController; private boolean mIsListening; @@ -111,7 +110,9 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> state.label = mContext.getString(R.string.reduce_bright_colors_feature_name); state.expandedAccessibilityClassName = Switch.class.getName(); state.contentDescription = state.label; - state.icon = mIcon; + state.icon = ResourceIcon.get(state.value + ? drawable.qs_extra_dim_icon_on + : drawable.qs_extra_dim_icon_off); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index 75fb393d84b5..f63f0444210b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -120,7 +120,9 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> state.value = isRecording || isStarting; state.state = (isRecording || isStarting) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.label = mContext.getString(R.string.quick_settings_screen_record_label); - state.icon = ResourceIcon.get(R.drawable.ic_screenrecord); + state.icon = ResourceIcon.get(state.value + ? R.drawable.qs_screen_record_icon_on + : R.drawable.qs_screen_record_icon_off); // Show expand icon when clicking will open a dialog state.forceExpandIcon = state.state == Tile.STATE_INACTIVE; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 2861ed7edf37..24c4723d8b50 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -198,6 +198,8 @@ public class InternetDialog extends SystemUIDialog implements mUiEventLogger.log(InternetDialogEvent.INTERNET_DIALOG_SHOW); mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog, null); + mDialogView.setAccessibilityPaneTitle( + mContext.getText(R.string.accessibility_desc_quick_settings)); final Window window = getWindow(); window.setContentView(mDialogView); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 3429b9d0e503..1011a6d831e6 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -33,6 +33,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_Q import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; +import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD; import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED; @@ -62,6 +63,7 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; +import android.os.Process; import android.os.Trace; import android.os.UserManager; import android.os.VibrationEffect; @@ -236,6 +238,9 @@ public final class NotificationPanelViewController extends PanelViewController { private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); private static final boolean DEBUG_DRAWABLE = false; + private static final VibrationEffect ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT = + VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false); + /** * The parallax amount of the quick settings translation when dragging down the panel */ @@ -682,14 +687,22 @@ public final class NotificationPanelViewController extends PanelViewController { private final FalsingTapListener mFalsingTapListener = new FalsingTapListener() { @Override - public void onDoubleTapRequired() { + public void onAdditionalTapRequired() { if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) { mTapAgainViewController.show(); } else { mKeyguardIndicationController.showTransientIndication( R.string.notification_tap_again); } - mVibratorHelper.vibrate(VibrationEffect.EFFECT_STRENGTH_MEDIUM); + + if (!mStatusBarStateController.isDozing()) { + mVibratorHelper.vibrate( + Process.myUid(), + mView.getContext().getPackageName(), + ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT, + "falsing-additional-tap-required", + TOUCH_VIBRATION_ATTRIBUTES); + } } }; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index abafecce2965..8d74a09ab8b1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -73,7 +73,7 @@ public class NotificationShadeWindowViewController { private final AmbientState mAmbientState; private final PulsingGestureListener mPulsingGestureListener; - private GestureDetector mGestureDetector; + private GestureDetector mPulsingWakeupGestureHandler; private View mBrightnessMirror; private boolean mTouchActive; private boolean mTouchCancelled; @@ -149,7 +149,8 @@ public class NotificationShadeWindowViewController { /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */ public void setupExpandedStatusBar() { mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller); - mGestureDetector = new GestureDetector(mView.getContext(), mPulsingGestureListener); + mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(), + mPulsingGestureListener); mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() { @Override @@ -196,7 +197,7 @@ public class NotificationShadeWindowViewController { } mFalsingCollector.onTouchEvent(ev); - mGestureDetector.onTouchEvent(ev); + mPulsingWakeupGestureHandler.onTouchEvent(ev); mStatusBarKeyguardViewManager.onTouch(ev); if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == View.VISIBLE) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt index 9b3fe92f9225..084b7dc3a646 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt @@ -26,6 +26,7 @@ import com.android.systemui.Dumpable import com.android.systemui.dock.DockManager import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.FalsingManager.LOW_PENALTY import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent @@ -36,12 +37,12 @@ import javax.inject.Inject /** * If tap and/or double tap to wake is enabled, this gestureListener will wake the display on - * tap/double tap when the device is pulsing (AoD 2). Taps are gated by the proximity sensor and - * falsing manager. + * tap/double tap when the device is pulsing (AoD2) or transitioning to AoD. Taps are gated by the + * proximity sensor and falsing manager. * - * Touches go through the [NotificationShadeWindowViewController] when the device is pulsing. - * Otherwise, if the device is dozing and NOT pulsing, wake-ups are handled by - * [com.android.systemui.doze.DozeSensors]. + * Touches go through the [NotificationShadeWindowViewController] when the device is dozing but the + * screen is still ON and not in the true AoD display state. When the device is in the true AoD + * display state, wake-ups are handled by [com.android.systemui.doze.DozeSensors]. */ @CentralSurfacesComponent.CentralSurfacesScope class PulsingGestureListener @Inject constructor( @@ -75,12 +76,12 @@ class PulsingGestureListener @Inject constructor( dumpManager.registerDumpable(this) } - override fun onSingleTapConfirmed(e: MotionEvent): Boolean { - if (statusBarStateController.isPulsing && + override fun onSingleTapUp(e: MotionEvent): Boolean { + if (statusBarStateController.isDozing && singleTapEnabled && !dockManager.isDocked && !falsingManager.isProximityNear && - !falsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY) + !falsingManager.isFalseTap(LOW_PENALTY) ) { centralSurfaces.wakeUpIfDozing( SystemClock.uptimeMillis(), @@ -91,8 +92,15 @@ class PulsingGestureListener @Inject constructor( return false } - override fun onDoubleTap(e: MotionEvent): Boolean { - if (statusBarStateController.isPulsing && + /** + * Receives [MotionEvent.ACTION_DOWN], [MotionEvent.ACTION_MOVE], and [MotionEvent.ACTION_UP] + * motion events for a double tap. + */ + override fun onDoubleTapEvent(e: MotionEvent): Boolean { + // React to the [MotionEvent.ACTION_UP] event after double tap is detected. Falsing + // checks MUST be on the ACTION_UP event. + if (e.actionMasked == MotionEvent.ACTION_UP && + statusBarStateController.isDozing && (doubleTapEnabled || singleTapEnabled) && !falsingManager.isProximityNear && !falsingManager.isFalseDoubleTap diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java index 867be1ab305f..c070fccf9808 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java @@ -40,7 +40,7 @@ import javax.inject.Inject; public class VibratorHelper { private final Vibrator mVibrator; - private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = + public static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); private static final VibrationEffect BIOMETRIC_SUCCESS_VIBRATION_EFFECT = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 985f3cd44808..6649f3a5bdfd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -667,9 +667,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private void setDozing(boolean dozing) { if (mDozing != dozing) { mDozing = dozing; - if (dozing || mBouncer.needsFullscreenBouncer() || mOccluded) { - reset(dozing /* hideBouncerWhenShowing */); - } + reset(true /* hideBouncerWhenShowing */); updateStates(); if (!dozing) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt deleted file mode 100644 index fe846747d0c6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.pipeline - -import android.content.Context -import com.android.systemui.CoreStartable -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel -import javax.inject.Inject -import javax.inject.Provider -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch - -/** - * A temporary object that collects on [WifiViewModel] flows for debugging purposes. - * - * This will eventually get migrated to a view binder that will use the flow outputs to set state on - * views. For now, this just collects on flows so that the information gets logged. - */ -@SysUISingleton -class ConnectivityInfoProcessor @Inject constructor( - context: Context, - // TODO(b/238425913): Don't use the application scope; instead, use the status bar view's - // scope so we only do work when there's UI that cares about it. - @Application private val scope: CoroutineScope, - private val statusBarPipelineFlags: StatusBarPipelineFlags, - private val wifiViewModelProvider: Provider<WifiViewModel>, -) : CoreStartable(context) { - override fun start() { - if (!statusBarPipelineFlags.isNewPipelineBackendEnabled()) { - return - } - // TODO(b/238425913): The view binder should do this instead. For now, do it here so we can - // see the logs. - scope.launch { - wifiViewModelProvider.get().isActivityInVisible.collect { } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 681dc6fc13a2..9a7c3fae780c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -16,25 +16,15 @@ package com.android.systemui.statusbar.pipeline.dagger -import com.android.systemui.CoreStartable -import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl import dagger.Binds import dagger.Module -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap @Module abstract class StatusBarPipelineModule { - /** Inject into ConnectivityInfoProcessor. */ - @Binds - @IntoMap - @ClassKey(ConnectivityInfoProcessor::class) - abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable - @Binds abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index e7fa6d239012..aae0f93a0e19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -27,7 +27,6 @@ import android.os.Looper; import android.os.Message; import android.os.UserHandle; import android.os.UserManager; -import android.util.Log; import androidx.annotation.NonNull; @@ -37,6 +36,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.systemui.bluetooth.BluetoothLogger; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -57,9 +57,9 @@ import javax.inject.Inject; public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback, CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener { private static final String TAG = "BluetoothController"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final DumpManager mDumpManager; + private final BluetoothLogger mLogger; private final LocalBluetoothManager mLocalBluetoothManager; private final UserManager mUserManager; private final int mCurrentUser; @@ -70,6 +70,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private final List<CachedBluetoothDevice> mConnectedDevices = new ArrayList<>(); private boolean mEnabled; + @ConnectionState private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED; private boolean mAudioProfileOnly; private boolean mIsActive; @@ -83,10 +84,12 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa public BluetoothControllerImpl( Context context, DumpManager dumpManager, + BluetoothLogger logger, @Background Looper bgLooper, @Main Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager) { mDumpManager = dumpManager; + mLogger = logger; mLocalBluetoothManager = localBluetoothManager; mBgHandler = new Handler(bgLooper); mHandler = new H(mainLooper); @@ -116,7 +119,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa return; } pw.print(" mEnabled="); pw.println(mEnabled); - pw.print(" mConnectionState="); pw.println(stateToString(mConnectionState)); + pw.print(" mConnectionState="); pw.println(connectionStateToString(mConnectionState)); pw.print(" mAudioProfileOnly="); pw.println(mAudioProfileOnly); pw.print(" mIsActive="); pw.println(mIsActive); pw.print(" mConnectedDevices="); pw.println(getConnectedDevices()); @@ -127,7 +130,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } } - private static String stateToString(int state) { + private static String connectionStateToString(@ConnectionState int state) { switch (state) { case BluetoothAdapter.STATE_CONNECTED: return "CONNECTED"; @@ -320,8 +323,8 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } @Override - public void onBluetoothStateChanged(int bluetoothState) { - if (DEBUG) Log.d(TAG, "BluetoothStateChanged=" + stateToString(bluetoothState)); + public void onBluetoothStateChanged(@AdapterState int bluetoothState) { + mLogger.logStateChange(BluetoothAdapter.nameForState(bluetoothState)); mEnabled = bluetoothState == BluetoothAdapter.STATE_ON || bluetoothState == BluetoothAdapter.STATE_TURNING_ON; mState = bluetoothState; @@ -330,24 +333,25 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } @Override - public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { - if (DEBUG) Log.d(TAG, "DeviceAdded=" + cachedDevice.getAddress()); + public void onDeviceAdded(@NonNull CachedBluetoothDevice cachedDevice) { + mLogger.logDeviceAdded(cachedDevice.getAddress()); cachedDevice.registerCallback(this); updateConnected(); mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); } @Override - public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { - if (DEBUG) Log.d(TAG, "DeviceDeleted=" + cachedDevice.getAddress()); + public void onDeviceDeleted(@NonNull CachedBluetoothDevice cachedDevice) { + mLogger.logDeviceDeleted(cachedDevice.getAddress()); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); } @Override - public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { - if (DEBUG) Log.d(TAG, "DeviceBondStateChanged=" + cachedDevice.getAddress()); + public void onDeviceBondStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int bondState) { + mLogger.logBondStateChange(cachedDevice.getAddress(), bondState); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); @@ -355,50 +359,47 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa @Override public void onDeviceAttributesChanged() { - if (DEBUG) Log.d(TAG, "DeviceAttributesChanged"); + mLogger.logDeviceAttributesChanged(); updateConnected(); mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED); } @Override - public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { - if (DEBUG) { - Log.d(TAG, "ConnectionStateChanged=" + cachedDevice.getAddress() + " " - + stateToString(state)); - } + public void onConnectionStateChanged( + @Nullable CachedBluetoothDevice cachedDevice, + @ConnectionState int state) { + mLogger.logDeviceConnectionStateChanged( + getAddressOrNull(cachedDevice), connectionStateToString(state)); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); } @Override - public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, - int state, int bluetoothProfile) { - if (DEBUG) { - Log.d(TAG, "ProfileConnectionStateChanged=" + cachedDevice.getAddress() + " " - + stateToString(state) + " profileId=" + bluetoothProfile); - } + public void onProfileConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, + @ConnectionState int state, + int bluetoothProfile) { + mLogger.logProfileConnectionStateChanged( + cachedDevice.getAddress(), connectionStateToString(state), bluetoothProfile); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); } @Override - public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) { - if (DEBUG) { - Log.d(TAG, "ActiveDeviceChanged=" + activeDevice.getAddress() - + " profileId=" + bluetoothProfile); - } + public void onActiveDeviceChanged( + @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { + mLogger.logActiveDeviceChanged(getAddressOrNull(activeDevice), bluetoothProfile); updateActive(); mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); } @Override - public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { - if (DEBUG) { - Log.d(TAG, "ACLConnectionStateChanged=" + cachedDevice.getAddress() + " " - + stateToString(state)); - } + public void onAclConnectionStateChanged( + @NonNull CachedBluetoothDevice cachedDevice, int state) { + mLogger.logAclConnectionStateChanged( + cachedDevice.getAddress(), connectionStateToString(state)); mCachedState.remove(cachedDevice); updateConnected(); mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); @@ -415,6 +416,11 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa return state; } + @Nullable + private String getAddressOrNull(@Nullable CachedBluetoothDevice device) { + return device == null ? null : device.getAddress(); + } + @Override public void onServiceConnected() { updateConnected(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 3b8ed338db80..63e88a6cde67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -1122,11 +1122,19 @@ public class UserSwitcherController implements Dumpable { } public String getName(Context context, UserRecord item) { + return getName(context, item, false); + } + + /** + * Returns the name for the given {@link UserRecord}. + */ + public String getName(Context context, UserRecord item, boolean isTablet) { return LegacyUserUiHelper.getUserRecordName( context, item, mController.isGuestUserAutoCreated(), - mController.isGuestUserResetting()); + mController.isGuestUserResetting(), + isTablet); } protected static ColorFilter getDisabledUserAvatarColorFilter() { @@ -1136,8 +1144,12 @@ public class UserSwitcherController implements Dumpable { } protected static Drawable getIconDrawable(Context context, UserRecord item) { + return getIconDrawable(context, item, false); + } + protected static Drawable getIconDrawable(Context context, UserRecord item, + boolean isTablet) { int iconRes = LegacyUserUiHelper.getUserSwitcherActionIconResourceId( - item.isAddUser, item.isGuest, item.isAddSupervisedUser); + item.isAddUser, item.isGuest, item.isAddSupervisedUser, isTablet); return context.getDrawable(iconRes); } diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt index d43f739f9e71..5e2dde6be046 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt @@ -173,8 +173,8 @@ open class UserSwitcherActivity @Inject constructor( this, R.layout.user_switcher_fullscreen_popup_item, layoutInflater, - { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item) }, - { item: UserRecord -> adapter.findUserIcon(item).mutate().apply { + { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) }, + { item: UserRecord -> adapter.findUserIcon(item, true).mutate().apply { setTint(resources.getColor( R.color.user_switcher_fullscreen_popup_item_tint, getTheme() @@ -322,6 +322,9 @@ open class UserSwitcherActivity @Inject constructor( return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY) } + /** + * Provides views to populate the option menu. + */ private class ItemAdapter( val parentContext: Context, val resource: Int, @@ -375,20 +378,20 @@ open class UserSwitcherActivity @Inject constructor( return view } - override fun getName(context: Context, item: UserRecord): String { + override fun getName(context: Context, item: UserRecord, isTablet: Boolean): String { return if (item == manageUserRecord) { getString(R.string.manage_users) } else { - super.getName(context, item) + super.getName(context, item, isTablet) } } - fun findUserIcon(item: UserRecord): Drawable { + fun findUserIcon(item: UserRecord, isTablet: Boolean = false): Drawable { if (item == manageUserRecord) { return getDrawable(R.drawable.ic_manage_users) } if (item.info == null) { - return getIconDrawable(this@UserSwitcherActivity, item) + return getIconDrawable(this@UserSwitcherActivity, item, isTablet) } val userIcon = userManager.getUserIcon(item.info.id) if (userIcon != null) { diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt index 18369d9a71d2..15fdc352d864 100644 --- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt @@ -49,8 +49,11 @@ object LegacyUserUiHelper { isAddUser: Boolean, isGuest: Boolean, isAddSupervisedUser: Boolean, + isTablet: Boolean = false, ): Int { - return if (isAddUser) { + return if (isAddUser && isTablet) { + R.drawable.ic_account_circle_filled + } else if (isAddUser) { R.drawable.ic_add } else if (isGuest) { R.drawable.ic_account_circle @@ -67,6 +70,7 @@ object LegacyUserUiHelper { record: UserRecord, isGuestUserAutoCreated: Boolean, isGuestUserResetting: Boolean, + isTablet: Boolean = false, ): String { val resourceId: Int? = getGuestUserRecordNameResourceId(record) return when { @@ -80,6 +84,7 @@ object LegacyUserUiHelper { isGuestUserResetting = isGuestUserResetting, isAddUser = record.isAddUser, isAddSupervisedUser = record.isAddSupervisedUser, + isTablet = isTablet, ) ) } @@ -108,12 +113,14 @@ object LegacyUserUiHelper { isGuestUserResetting: Boolean, isAddUser: Boolean, isAddSupervisedUser: Boolean, + isTablet: Boolean = false, ): Int { check(isGuest || isAddUser || isAddSupervisedUser) return when { isGuest && isGuestUserAutoCreated && isGuestUserResetting -> com.android.settingslib.R.string.guest_resetting + isGuest && isTablet -> com.android.settingslib.R.string.guest_new_guest isGuest && isGuestUserAutoCreated -> com.android.internal.R.string.guest_name isGuest -> com.android.internal.R.string.guest_name isAddUser -> com.android.settingslib.R.string.user_add_user 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 83a3d0d0457a..d7ad3cefaf06 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 @@ -129,7 +129,10 @@ object UserSwitcherViewBinder { viewModel.users.collect { users -> val viewPool = view.children.filter { it.tag == USER_VIEW_TAG }.toMutableList() - viewPool.forEach { view.removeView(it) } + viewPool.forEach { + view.removeView(it) + flowWidget.removeView(it) + } users.forEach { userViewModel -> val userView = if (viewPool.isNotEmpty()) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index c7ba5182eef8..903aba1a74a4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -48,6 +48,7 @@ import android.app.KeyguardManager; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -74,6 +75,7 @@ import android.os.VibrationEffect; import android.provider.Settings; import android.provider.Settings.Global; import android.text.InputFilter; +import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; @@ -1049,7 +1051,13 @@ public class VolumeDialogImpl implements VolumeDialog, Events.writeEvent(Events.EVENT_SETTINGS_CLICK); dismissH(DISMISS_REASON_SETTINGS_CLICKED); mMediaOutputDialogFactory.dismiss(); - mVolumePanelFactory.create(true /* aboveStatusBar */, null); + if (FeatureFlagUtils.isEnabled(mContext, + FeatureFlagUtils.SETTINGS_VOLUME_PANEL_IN_SYSTEMUI)) { + mVolumePanelFactory.create(true /* aboveStatusBar */, null); + } else { + mActivityStarter.startActivity(new Intent(Settings.Panel.ACTION_VOLUME), + true /* dismissShade */); + } }); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 914d94522e83..25e7dbb5f26d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -119,6 +119,20 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test + fun fontChanged_verifyFontSizeUpdated() { + clockEventController.clock = clock + verify(events).onColorPaletteChanged(any(), any(), any()) + + clockEventController.registerListeners() + + val captor = argumentCaptor<ConfigurationController.ConfigurationListener>() + verify(configurationController).addCallback(capture(captor)) + captor.value.onDensityOrFontScaleChanged() + + verify(events).onFontSettingChanged() + } + + @Test fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() { clockEventController.clock = clock clockEventController.registerListeners() diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 508f81d4ee8c..e3c71282ffc5 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -329,8 +329,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @After public void tearDown() { mMockitoSession.finishMocking(); - mKeyguardUpdateMonitor.removeCallback(mTestCallback); - mKeyguardUpdateMonitor.destroy(); + cleanupKeyguardUpdateMonitor(); } @Test @@ -352,6 +351,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testSimStateInitialized() { + cleanupKeyguardUpdateMonitor(); final int subId = 3; final int state = TelephonyManager.SIM_STATE_ABSENT; @@ -1206,7 +1206,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenFaceManagerNotAvailable_returnsFalse() { - mFaceManager = null; + cleanupKeyguardUpdateMonitor(); + mSpiedContext.addMockSystemService(FaceManager.class, null); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false); mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext); assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); @@ -1259,6 +1261,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testShouldListenForFace_whenUserIsNotPrimary_returnsFalse() throws RemoteException { + cleanupKeyguardUpdateMonitor(); // This disables face auth when(mUserManager.isPrimaryUser()).thenReturn(false); mKeyguardUpdateMonitor = @@ -1588,9 +1591,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testFingerAcquired_wakesUpPowerManager() { - mContext.getOrCreateTestableResources().addOverride( + cleanupKeyguardUpdateMonitor(); + mSpiedContext.getOrCreateTestableResources().addOverride( com.android.internal.R.bool.kg_wake_on_acquire_start, true); - mSpiedContext = spy(mContext); mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext); fingerprintAcquireStart(); @@ -1599,15 +1602,23 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testFingerAcquired_doesNotWakeUpPowerManager() { - mContext.getOrCreateTestableResources().addOverride( + cleanupKeyguardUpdateMonitor(); + mSpiedContext.getOrCreateTestableResources().addOverride( com.android.internal.R.bool.kg_wake_on_acquire_start, false); - mSpiedContext = spy(mContext); mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext); fingerprintAcquireStart(); verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString()); } + private void cleanupKeyguardUpdateMonitor() { + if (mKeyguardUpdateMonitor != null) { + mKeyguardUpdateMonitor.removeCallback(mTestCallback); + mKeyguardUpdateMonitor.destroy(); + mKeyguardUpdateMonitor = null; + } + } + private void faceAuthLockedOut() { mKeyguardUpdateMonitor.mFaceAuthenticationCallback .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, ""); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index bf3788e4c76a..4a5b23c02e40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -29,6 +29,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.testing.ViewUtils +import android.view.KeyEvent import android.view.View import android.view.WindowInsets import android.view.WindowManager @@ -92,6 +93,21 @@ class AuthContainerViewTest : SysuiTestCase() { } @Test + fun testDismissesOnBack() { + val container = initializeFingerprintContainer(addToView = true) + assertThat(container.parent).isNotNull() + val root = container.rootView + + // Simulate back invocation + container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)) + container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)) + waitForIdleSync() + + assertThat(container.parent).isNull() + assertThat(root.isAttachedToWindow).isFalse() + } + + @Test fun testIgnoresAnimatedInWhenDismissed() { val container = initializeFingerprintContainer(addToView = false) container.dismissFromSystemServer() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index 44ef922d2c39..37bb0c296735 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.biometrics import android.graphics.Point import android.hardware.biometrics.BiometricSourceType +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest @@ -76,6 +77,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Mock private lateinit var udfpsController: UdfpsController @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var lightRevealScrim: LightRevealScrim + @Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal @Before fun setUp() { @@ -86,6 +88,7 @@ class AuthRippleControllerTest : SysuiTestCase() { .startMocking() `when`(RotationUtils.getRotation(context)).thenReturn(RotationUtils.ROTATION_NONE) + `when`(authController.udfpsProps).thenReturn(listOf(fpSensorProp)) `when`(udfpsControllerProvider.get()).thenReturn(udfpsController) controller = AuthRippleController( @@ -132,7 +135,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN update sensor location and show ripple - verify(rippleView).setFingerprintSensorLocation(fpsLocation, -1f) + verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f) verify(rippleView).startUnlockedRipple(any()) } @@ -155,7 +158,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN update sensor location and show ripple - verify(rippleView).setFingerprintSensorLocation(fpsLocation, -1f) + verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f) verify(rippleView).startUnlockedRipple(any()) } @@ -342,4 +345,23 @@ class AuthRippleControllerTest : SysuiTestCase() { captor.value.onUiModeChanged() verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt()) } + + @Test + fun testUdfps_onFingerDown_showDwellRipple() { + // GIVEN view is already attached + controller.onViewAttached() + val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java) + verify(udfpsController).addCallback(captor.capture()) + + // GIVEN fp is updated to Point(5, 5) + val fpsLocation = Point(5, 5) + `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) + + // WHEN finger is down + captor.value.onFingerDown() + + // THEN update sensor location and show ripple + verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f) + verify(rippleView).startDwellRipple(false) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index cb8358dd22cc..5c564e65ea86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -17,23 +17,13 @@ package com.android.systemui.biometrics import android.graphics.Rect -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER -import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS -import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING -import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR -import android.hardware.biometrics.BiometricOverlayConstants.ShowReason +import android.hardware.biometrics.BiometricOverlayConstants.* import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.IUdfpsOverlayControllerCallback import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper -import android.view.LayoutInflater -import android.view.MotionEvent -import android.view.View -import android.view.Surface +import android.view.* import android.view.Surface.Rotation -import android.view.WindowManager import android.view.accessibility.AccessibilityManager import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor @@ -65,7 +55,6 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever -private const val HAL_CONTROLS_ILLUMINATION = true private const val REQUEST_ID = 2L // Dimensions for the current display resolution. @@ -95,8 +84,9 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var configurationController: ConfigurationController @Mock private lateinit var systemClock: SystemClock @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock private lateinit var unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController - @Mock private lateinit var hbmProvider: UdfpsHbmProvider + @Mock private lateinit var unlockedScreenOffAnimationController: + UnlockedScreenOffAnimationController + @Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider @Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback @Mock private lateinit var udfpsController: UdfpsController @Mock private lateinit var udfpsView: UdfpsView @@ -130,8 +120,9 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { statusBarStateController, panelExpansionStateManager, statusBarKeyguardViewManager, keyguardUpdateMonitor, dialogManager, dumpManager, transitionController, configurationController, systemClock, keyguardStateController, - unlockedScreenOffAnimationController, HAL_CONTROLS_ILLUMINATION, hbmProvider, - REQUEST_ID, reason, controllerCallback, onTouch, activityLaunchAnimator) + unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason, + controllerCallback, onTouch, activityLaunchAnimator + ) block() } @@ -246,7 +237,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { val didShow = controllerOverlay.show(udfpsController, overlayParams) verify(windowManager).addView(eq(controllerOverlay.overlayView), any()) - verify(udfpsView).setHbmProvider(eq(hbmProvider)) + verify(udfpsView).setUdfpsDisplayModeProvider(eq(udfpsDisplayMode)) verify(udfpsView).animationViewController = any() verify(udfpsView).addView(any()) @@ -351,12 +342,12 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { } @Test - fun stopIlluminatingOnHide() = withReason(REASON_AUTH_BP) { - whenever(udfpsView.isIlluminationRequested).thenReturn(true) + fun unconfigureDisplayOnHide() = withReason(REASON_AUTH_BP) { + whenever(udfpsView.isDisplayConfigured).thenReturn(true) controllerOverlay.show(udfpsController, overlayParams) controllerOverlay.hide() - verify(udfpsView).stopIllumination() + verify(udfpsView).unconfigureDisplay() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index d7a0a0ae974d..a6c0539a8281 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -125,7 +125,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private WindowManager mWindowManager; @Mock - private UdfpsHbmProvider mHbmProvider; + private UdfpsDisplayModeProvider mDisplayModeProvider; @Mock private StatusBarStateController mStatusBarStateController; @Mock @@ -193,7 +193,7 @@ public class UdfpsControllerTest extends SysuiTestCase { private IUdfpsOverlayController mOverlayController; @Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor; @Captor private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor; - @Captor private ArgumentCaptor<Runnable> mOnIlluminatedRunnableCaptor; + @Captor private ArgumentCaptor<Runnable> mOnDisplayConfiguredCaptor; @Captor private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor; private ScreenLifecycle.Observer mScreenObserver; @@ -256,7 +256,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mVibrator, mUdfpsHapticsSimulator, mUdfpsShell, - Optional.of(mHbmProvider), + Optional.of(mDisplayModeProvider), mKeyguardStateController, mDisplayManager, mHandler, @@ -512,7 +512,7 @@ public class UdfpsControllerTest extends SysuiTestCase { final float expectedMajor = touchMajor / scaleFactor; // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isIlluminationRequested()).thenReturn(false); + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); // Show the overlay. @@ -590,7 +590,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void fingerDown() throws RemoteException { // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isIlluminationRequested()).thenReturn(false); + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); @@ -617,12 +617,12 @@ public class UdfpsControllerTest extends SysuiTestCase { verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat()); verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - // AND illumination begins - verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture()); + // AND display configuration begins + verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture()); verify(mLatencyTracker, never()).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID)); - // AND onIlluminatedRunnable notifies FingerprintManager about onUiReady - mOnIlluminatedRunnableCaptor.getValue().run(); + // AND onDisplayConfigured notifies FingerprintManager about onUiReady + mOnDisplayConfiguredCaptor.getValue().run(); mBiometricsExecutor.runAllReady(); InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker); inOrder.verify(mAlternateTouchProvider).onUiReady(); @@ -640,10 +640,10 @@ public class UdfpsControllerTest extends SysuiTestCase { // WHEN fingerprint is requested because of AOD interrupt mUdfpsController.onAodInterrupt(0, 0, 2f, 3f); mFgExecutor.runAllReady(); - // THEN illumination begins - // AND onIlluminatedRunnable that notifies FingerprintManager is set - verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture()); - mOnIlluminatedRunnableCaptor.getValue().run(); + // THEN display configuration begins + // AND onDisplayConfigured notifies FingerprintManager about onUiReady + verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture()); + mOnDisplayConfiguredCaptor.getValue().run(); mBiometricsExecutor.runAllReady(); verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(3f) /* minor */, eq(2f) /* major */); @@ -661,11 +661,11 @@ public class UdfpsControllerTest extends SysuiTestCase { mFgExecutor.runAllReady(); when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - when(mUdfpsView.isIlluminationRequested()).thenReturn(true); + when(mUdfpsView.isDisplayConfigured()).thenReturn(true); // WHEN it is cancelled mUdfpsController.onCancelUdfps(); - // THEN the illumination is hidden - verify(mUdfpsView).stopIllumination(); + // THEN the display is unconfigured + verify(mUdfpsView).unconfigureDisplay(); } @Test @@ -678,12 +678,12 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); mFgExecutor.runAllReady(); - when(mUdfpsView.isIlluminationRequested()).thenReturn(true); + when(mUdfpsView.isDisplayConfigured()).thenReturn(true); // WHEN it times out mFgExecutor.advanceClockToNext(); mFgExecutor.runAllReady(); - // THEN the illumination is hidden - verify(mUdfpsView).stopIllumination(); + // THEN the display is unconfigured + verify(mUdfpsView).unconfigureDisplay(); } @Test @@ -698,8 +698,8 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - // THEN no illumination because screen is off - verify(mUdfpsView, never()).startIllumination(any()); + // THEN display doesn't get configured because it's off + verify(mUdfpsView, never()).configureDisplay(any()); } @Test @@ -715,14 +715,14 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - // THEN no illumination because screen is off - verify(mUdfpsView, never()).startIllumination(any()); + // THEN display doesn't get configured because it's off + verify(mUdfpsView, never()).configureDisplay(any()); } @Test public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled() throws RemoteException { // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isIlluminationRequested()).thenReturn(false); + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); // GIVEN that the overlay is showing and a11y touch exploration enabled @@ -757,7 +757,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException { // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isIlluminationRequested()).thenReturn(false); + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); // GIVEN that the overlay is showing and a11y touch exploration NOT enabled diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt index 0327cfcf3450..b78c06391057 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt @@ -36,13 +36,12 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.nullable import org.mockito.Mockito.verify -import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit private const val SENSOR_X = 50 private const val SENSOR_Y = 250 @@ -57,7 +56,7 @@ class UdfpsViewTest : SysuiTestCase() { var rule = MockitoJUnit.rule() @Mock - lateinit var hbmProvider: UdfpsHbmProvider + lateinit var hbmProvider: UdfpsDisplayModeProvider @Mock lateinit var animationViewController: UdfpsAnimationViewController<UdfpsAnimationView> @@ -66,13 +65,11 @@ class UdfpsViewTest : SysuiTestCase() { @Before fun setup() { context.setTheme(R.style.Theme_AppCompat) - context.orCreateTestableResources.addOverride( - com.android.internal.R.integer.config_udfps_illumination_transition_ms, 0) view = LayoutInflater.from(context).inflate(R.layout.udfps_view, null) as UdfpsView view.animationViewController = animationViewController val sensorBounds = SensorLocationInternal("", SENSOR_X, SENSOR_Y, SENSOR_RADIUS).rect view.overlayParams = UdfpsOverlayParams(sensorBounds, 1920, 1080, 1f, Surface.ROTATION_0) - view.setHbmProvider(hbmProvider) + view.setUdfpsDisplayModeProvider(hbmProvider) ViewUtils.attachView(view) } @@ -143,27 +140,27 @@ class UdfpsViewTest : SysuiTestCase() { @Test fun startAndStopIllumination() { val onDone: Runnable = mock() - view.startIllumination(onDone) + view.configureDisplay(onDone) val illuminator = withArgCaptor<Runnable> { - verify(hbmProvider).enableHbm(anyBoolean(), capture()) + verify(hbmProvider).enable(capture()) } - assertThat(view.isIlluminationRequested).isTrue() - verify(animationViewController).onIlluminationStarting() - verify(animationViewController, never()).onIlluminationStopped() + assertThat(view.isDisplayConfigured).isTrue() + verify(animationViewController).onDisplayConfiguring() + verify(animationViewController, never()).onDisplayUnconfigured() verify(onDone, never()).run() // fake illumination event illuminator.run() waitForLooper() verify(onDone).run() - verify(hbmProvider, never()).disableHbm(any()) + verify(hbmProvider, never()).disable(any()) - view.stopIllumination() - assertThat(view.isIlluminationRequested).isFalse() - verify(animationViewController).onIlluminationStopped() - verify(hbmProvider).disableHbm(nullable(Runnable::class.java)) + view.unconfigureDisplay() + assertThat(view.isDisplayConfigured).isFalse() + verify(animationViewController).onDisplayUnconfigured() + verify(hbmProvider).disable(nullable(Runnable::class.java)) } private fun waitForLooper() = TestableLooper.get(this).processAllMessages() diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java index acb5622c9790..3e9cf1e51b63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java @@ -229,7 +229,10 @@ public class FalsingCollectorImplTest extends SysuiTestCase { } @Test - public void testAvoidDozingNotPulsing() { + public void testGestureWhenDozing() { + // We check the FalsingManager for taps during the transition to AoD (dozing=true, + // pulsing=false), so the FalsingCollector needs to continue to analyze events that occur + // while the device is dozing. MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); @@ -239,13 +242,13 @@ public class FalsingCollectorImplTest extends SysuiTestCase { mFalsingCollector.onTouchEvent(down); verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class)); - // Up event would normally flush the up event, but doesn't. + // Up event flushes mFalsingCollector.onTouchEvent(up); - verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class)); + verify(mFalsingDataProvider, times(2)).onMotionEvent(any(MotionEvent.class)); } @Test - public void testAvoidDozingButPulsing() { + public void testGestureWhenPulsing() { MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java new file mode 100644 index 000000000000..08fe7c486529 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.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 com.android.systemui.clipboardoverlay; + +import android.content.ClipData; +import android.content.ComponentName; +import android.content.Intent; +import android.net.Uri; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class IntentCreatorTest extends SysuiTestCase { + private static final int EXTERNAL_INTENT_FLAGS = Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION; + + @Test + public void test_getTextEditorIntent() { + Intent intent = IntentCreator.getTextEditorIntent(getContext()); + assertEquals(new ComponentName(getContext(), EditTextActivity.class), + intent.getComponent()); + assertFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + } + + @Test + public void test_getRemoteCopyIntent() { + getContext().getOrCreateTestableResources().addOverride(R.string.config_remoteCopyPackage, + ""); + + ClipData clipData = ClipData.newPlainText("Test", "Test Item"); + Intent intent = IntentCreator.getRemoteCopyIntent(clipData, getContext()); + + assertEquals(null, intent.getComponent()); + assertFlags(intent, EXTERNAL_INTENT_FLAGS); + assertEquals(clipData, intent.getClipData()); + + // Try again with a remote copy component + ComponentName fakeComponent = new ComponentName("com.android.remotecopy", + "com.android.remotecopy.RemoteCopyActivity"); + getContext().getOrCreateTestableResources().addOverride(R.string.config_remoteCopyPackage, + fakeComponent.flattenToString()); + + intent = IntentCreator.getRemoteCopyIntent(clipData, getContext()); + assertEquals(fakeComponent, intent.getComponent()); + } + + @Test + public void test_getImageEditIntent() { + getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, + ""); + Uri fakeUri = Uri.parse("content://foo"); + Intent intent = IntentCreator.getImageEditIntent(fakeUri, getContext()); + + assertEquals(Intent.ACTION_EDIT, intent.getAction()); + assertEquals("image/*", intent.getType()); + assertEquals(null, intent.getComponent()); + assertFlags(intent, EXTERNAL_INTENT_FLAGS); + + // try again with an editor component + ComponentName fakeComponent = new ComponentName("com.android.remotecopy", + "com.android.remotecopy.RemoteCopyActivity"); + getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, + fakeComponent.flattenToString()); + intent = IntentCreator.getImageEditIntent(fakeUri, getContext()); + assertEquals(fakeComponent, intent.getComponent()); + } + + @Test + public void test_getShareIntent_plaintext() { + ClipData clipData = ClipData.newPlainText("Test", "Test Item"); + Intent intent = IntentCreator.getShareIntent(clipData, getContext()); + + assertEquals(Intent.ACTION_CHOOSER, intent.getAction()); + assertFlags(intent, EXTERNAL_INTENT_FLAGS); + Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class); + assertEquals("Test Item", target.getStringExtra(Intent.EXTRA_TEXT)); + assertEquals("text/plain", target.getType()); + } + + @Test + public void test_getShareIntent_html() { + ClipData clipData = ClipData.newHtmlText("Test", "Some HTML", + "<b>Some HTML</b>"); + Intent intent = IntentCreator.getShareIntent(clipData, getContext()); + + assertEquals(Intent.ACTION_CHOOSER, intent.getAction()); + assertFlags(intent, EXTERNAL_INTENT_FLAGS); + Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class); + assertEquals("Some HTML", target.getStringExtra(Intent.EXTRA_TEXT)); + assertEquals("text/plain", target.getType()); + } + + @Test + public void test_getShareIntent_image() { + Uri uri = Uri.parse("content://something"); + ClipData clipData = new ClipData("Test", new String[]{"image/png"}, + new ClipData.Item(uri)); + Intent intent = IntentCreator.getShareIntent(clipData, getContext()); + + assertEquals(Intent.ACTION_CHOOSER, intent.getAction()); + assertFlags(intent, EXTERNAL_INTENT_FLAGS); + Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class); + assertEquals(uri, target.getData()); + assertEquals("image/png", target.getType()); + } + + // Assert that the given flags are set + private void assertFlags(Intent intent, int flags) { + assertTrue((intent.getFlags() & flags) == flags); + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index bef46953395b..7de5719c03ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -591,14 +591,20 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindAlbumView_bitmapInLaterStates_setAfterExecutors() { - val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bmp) - canvas.drawColor(Color.RED) - val albumArt = Icon.createWithBitmap(bmp) + val redBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) + val redCanvas = Canvas(redBmp) + redCanvas.drawColor(Color.RED) + val redArt = Icon.createWithBitmap(redBmp) + + val greenBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888) + val greenCanvas = Canvas(greenBmp) + greenCanvas.drawColor(Color.GREEN) + val greenArt = Icon.createWithBitmap(greenBmp) val state0 = mediaData.copy(artwork = null) - val state1 = mediaData.copy(artwork = albumArt) - val state2 = mediaData.copy(artwork = albumArt) + val state1 = mediaData.copy(artwork = redArt) + val state2 = mediaData.copy(artwork = redArt) + val state3 = mediaData.copy(artwork = greenArt) player.attachPlayer(viewHolder) // First binding sets (empty) drawable @@ -627,6 +633,12 @@ public class MediaControlPanelTest : SysuiTestCase() { bgExecutor.runAllReady() mainExecutor.runAllReady() verify(albumView, times(2)).setImageDrawable(any(Drawable::class.java)) + + // Fourth binding to new image runs transition due to color scheme change + player.bindPlayer(state3, PACKAGE) + bgExecutor.runAllReady() + mainExecutor.runAllReady() + verify(albumView, times(3)).setImageDrawable(any(Drawable::class.java)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java index bf682a8d4e0a..3fd25019e2a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java @@ -33,12 +33,15 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.SecureSettings; @@ -54,6 +57,8 @@ import org.mockito.MockitoAnnotations; @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class ColorInversionTileTest extends SysuiTestCase { + private static final Integer COLOR_INVERSION_DISABLED = 0; + private static final Integer COLOR_INVERSION_ENABLED = 1; @Mock private QSTileHost mHost; @@ -113,4 +118,24 @@ public class ColorInversionTileTest extends SysuiTestCase { assertThat(IntentCaptor.getValue().getAction()).isEqualTo( Settings.ACTION_COLOR_INVERSION_SETTINGS); } + + @Test + public void testIcon_whenColorInversionDisabled_isOffState() { + QSTile.BooleanState state = new QSTile.BooleanState(); + + mTile.handleUpdateState(state, COLOR_INVERSION_DISABLED); + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_invert_colors_icon_off)); + } + + @Test + public void testIcon_whenColorInversionEnabled_isOnState() { + QSTile.BooleanState state = new QSTile.BooleanState(); + + mTile.handleUpdateState(state, COLOR_INVERSION_ENABLED); + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_invert_colors_icon_on)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt new file mode 100644 index 000000000000..ce62f2d1cf36 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles + +import android.os.Handler +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.statusbar.policy.DataSaverController +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class DataSaverTileTest : SysuiTestCase() { + + @Mock private lateinit var mHost: QSHost + @Mock private lateinit var mMetricsLogger: MetricsLogger + @Mock private lateinit var mStatusBarStateController: StatusBarStateController + @Mock private lateinit var mActivityStarter: ActivityStarter + @Mock private lateinit var mQsLogger: QSLogger + private val falsingManager = FalsingManagerFake() + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var dataSaverController: DataSaverController + @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + + private val uiEventLogger = UiEventLoggerFake() + private lateinit var testableLooper: TestableLooper + private lateinit var tile: DataSaverTile + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + + Mockito.`when`(mHost.context).thenReturn(mContext) + Mockito.`when`(mHost.uiEventLogger).thenReturn(uiEventLogger) + + tile = + DataSaverTile( + mHost, + testableLooper.looper, + Handler(testableLooper.looper), + falsingManager, + mMetricsLogger, + statusBarStateController, + activityStarter, + mQsLogger, + dataSaverController, + dialogLaunchAnimator + ) + } + + @Test + fun testIcon_whenDataSaverEnabled_isOnState() { + val state = QSTile.BooleanState() + + tile.handleUpdateState(state, true) + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_data_saver_icon_on)) + } + + @Test + fun testIcon_whenDataSaverDisabled_isOffState() { + val state = QSTile.BooleanState() + + tile.handleUpdateState(state, false) + + assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_data_saver_icon_off)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt new file mode 100644 index 000000000000..d0f851bded75 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt @@ -0,0 +1,108 @@ +package com.android.systemui.qs.tiles + +import android.content.Context +import android.os.Handler +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSTileHost +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.statusbar.policy.FlashlightController +import com.google.common.truth.Truth +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class FlashlightTileTest : SysuiTestCase() { + + @Mock private lateinit var mockContext: Context + + @Mock private lateinit var qsLogger: QSLogger + + @Mock private lateinit var qsHost: QSTileHost + + @Mock private lateinit var metricsLogger: MetricsLogger + + @Mock private lateinit var statusBarStateController: StatusBarStateController + + @Mock private lateinit var activityStarter: ActivityStarter + + @Mock private lateinit var flashlightController: FlashlightController + + private val uiEventLogger = UiEventLoggerFake() + private val falsingManager = FalsingManagerFake() + private lateinit var testableLooper: TestableLooper + private lateinit var tile: FlashlightTile + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + + Mockito.`when`(qsHost.context).thenReturn(mockContext) + Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) + + tile = + FlashlightTile( + qsHost, + testableLooper.looper, + Handler(testableLooper.looper), + falsingManager, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger, + flashlightController + ) + } + + @Test + fun testIcon_whenFlashlightEnabled_isOnState() { + Mockito.`when`(flashlightController.isAvailable).thenReturn(true) + Mockito.`when`(flashlightController.isEnabled).thenReturn(true) + val state = QSTile.BooleanState() + + tile.handleUpdateState(state, /* arg= */ null) + + Truth.assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_on)) + } + + @Test + fun testIcon_whenFlashlightDisabled_isOffState() { + Mockito.`when`(flashlightController.isAvailable).thenReturn(true) + Mockito.`when`(flashlightController.isEnabled).thenReturn(false) + val state = QSTile.BooleanState() + + tile.handleUpdateState(state, /* arg= */ null) + + Truth.assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_off)) + } + + @Test + fun testIcon_whenFlashlightUnavailable_isOffState() { + Mockito.`when`(flashlightController.isAvailable).thenReturn(false) + val state = QSTile.BooleanState() + + tile.handleUpdateState(state, /* arg= */ null) + + Truth.assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_off)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt new file mode 100644 index 000000000000..188c3a3d9e42 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles + +import android.hardware.display.ColorDisplayManager +import android.hardware.display.NightDisplayListener +import android.os.Handler +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.dagger.NightDisplayListenerModule +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.statusbar.policy.LocationController +import com.google.common.truth.Truth +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class NightDisplayTileTest : SysuiTestCase() { + @Mock + private lateinit var mHost: QSHost + + @Mock + private lateinit var mMetricsLogger: MetricsLogger + + @Mock + private lateinit var mStatusBarStateController: StatusBarStateController + + @Mock + private lateinit var mActivityStarter: ActivityStarter + + @Mock + private lateinit var mQsLogger: QSLogger + + @Mock + private lateinit var mLocationController: LocationController + + @Mock + private lateinit var mColorDisplayManager: ColorDisplayManager + + @Mock + private lateinit var mNightDisplayListenerBuilder: NightDisplayListenerModule.Builder + + @Mock + private lateinit var mNightDisplayListener: NightDisplayListener + + private lateinit var mTestableLooper: TestableLooper + private lateinit var mTile: NightDisplayTile + + private val mUiEventLogger: UiEventLogger = UiEventLoggerFake() + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + mTestableLooper = TestableLooper.get(this) + whenever(mHost.context).thenReturn(mContext) + whenever(mHost.uiEventLogger).thenReturn(mUiEventLogger) + whenever(mHost.userContext).thenReturn(mContext) + whenever(mNightDisplayListenerBuilder.setUser(anyInt())).thenReturn( + mNightDisplayListenerBuilder + ) + whenever(mNightDisplayListenerBuilder.build()).thenReturn(mNightDisplayListener) + + mTile = NightDisplayTile( + mHost, + mTestableLooper.looper, + Handler(mTestableLooper.looper), + FalsingManagerFake(), + mMetricsLogger, + mStatusBarStateController, + mActivityStarter, + mQsLogger, + mLocationController, + mColorDisplayManager, + mNightDisplayListenerBuilder + ) + } + + @Test + fun testIcon_whenDisabled_showsOffState() { + whenever(mColorDisplayManager.isNightDisplayActivated).thenReturn(false) + val state = QSTile.BooleanState() + + mTile.handleUpdateState(state, /* arg= */ null) + + Truth.assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_nightlight_icon_off)) + } + + @Test + fun testIcon_whenEnabled_showsOnState() { + whenever(mColorDisplayManager.isNightDisplayActivated).thenReturn(true) + val state = QSTile.BooleanState() + + mTile.handleUpdateState(state, /* arg= */ null) + + Truth.assertThat(state.icon) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_nightlight_icon_on)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java index 9eb688de3511..8601d6c0a357 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java @@ -32,13 +32,16 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.internal.logging.MetricsLogger; +import com.android.systemui.R.drawable; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; import org.junit.Before; @@ -130,4 +133,26 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase { .setReduceBrightColorsActivated(eq(true)); } + @Test + public void testIcon_whenTileEnabled_isOnState() { + when(mReduceBrightColorsController.isReduceBrightColorsActivated()).thenReturn(true); + mTile.refreshState(); + QSTile.BooleanState state = new QSTile.BooleanState(); + + mTile.handleUpdateState(state, /* arg= */ null); + + assertEquals(state.icon, QSTileImpl.ResourceIcon.get(drawable.qs_extra_dim_icon_on)); + } + + @Test + public void testIcon_whenTileDisabled_isOffState() { + when(mReduceBrightColorsController.isReduceBrightColorsActivated()).thenReturn(false); + mTile.refreshState(); + QSTile.BooleanState state = new QSTile.BooleanState(); + + mTile.handleUpdateState(state, /* arg= */ null); + + assertEquals(state.icon, QSTileImpl.ResourceIcon.get(drawable.qs_extra_dim_icon_off)); + } + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java index e6bd396ebc6c..30debdf4b744 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java @@ -41,9 +41,11 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -229,4 +231,38 @@ public class ScreenRecordTileTest extends SysuiTestCase { assertFalse(mTile.getState().forceExpandIcon); } + + @Test + public void testIcon_whenRecording_isOnState() { + when(mController.isStarting()).thenReturn(false); + when(mController.isRecording()).thenReturn(true); + QSTile.BooleanState state = new QSTile.BooleanState(); + + mTile.handleUpdateState(state, /* arg= */ null); + + assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_on)); + } + + @Test + public void testIcon_whenStarting_isOnState() { + when(mController.isStarting()).thenReturn(true); + when(mController.isRecording()).thenReturn(false); + QSTile.BooleanState state = new QSTile.BooleanState(); + + mTile.handleUpdateState(state, /* arg= */ null); + + assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_on)); + } + + @Test + public void testIcon_whenRecordingOff_isOffState() { + when(mController.isStarting()).thenReturn(false); + when(mController.isRecording()).thenReturn(false); + QSTile.BooleanState state = new QSTile.BooleanState(); + + mTile.handleUpdateState(state, /* arg= */ null); + + assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_off)); + } + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index d09a5a11040f..f92247580df0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -143,6 +143,12 @@ public class InternetDialogTest extends SysuiTestCase { } @Test + public void createInternetDialog_setAccessibilityPaneTitleToQuickSettings() { + assertThat(mDialogView.getAccessibilityPaneTitle()) + .isEqualTo(mContext.getText(R.string.accessibility_desc_quick_settings)); + } + + @Test public void hideWifiViews_WifiViewsGone() { mInternetDialog.hideWifiViews(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt index 6ce9cff9a730..002ef2962b03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -34,6 +34,7 @@ import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION +import androidx.test.filters.SmallTest import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.util.ScreenshotHelper import com.android.internal.util.ScreenshotHelper.ScreenshotRequest @@ -65,6 +66,7 @@ private const val USER_ID = 1 private const val TASK_ID = 1 @RunWith(AndroidTestingRunner::class) +@SmallTest class TakeScreenshotServiceTest : SysuiTestCase() { private val application = mock<Application>() 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 e73071335c9d..b40d5ac69d7b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -1022,7 +1022,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { FalsingManager.FalsingTapListener listener = getFalsingTapListener(); mStatusBarStateController.setState(KEYGUARD); - listener.onDoubleTapRequired(); + listener.onAdditionalTapRequired(); verify(mKeyguardIndicationController).showTransientIndication(anyInt()); } @@ -1032,7 +1032,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { FalsingManager.FalsingTapListener listener = getFalsingTapListener(); mStatusBarStateController.setState(SHADE_LOCKED); - listener.onDoubleTapRequired(); + listener.onAdditionalTapRequired(); verify(mTapAgainViewController).show(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt index 97c0bb2c09ca..09add652483e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt @@ -89,7 +89,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { @Test fun testGestureDetector_singleTapEnabled() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN tap is enabled, prox not covered whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) @@ -100,7 +100,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false) // WHEN there's a tap - underTest.onSingleTapConfirmed(downEv) + underTest.onSingleTapUp(upEv) // THEN wake up device if dozing verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString()) @@ -108,7 +108,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { @Test fun testGestureDetector_doubleTapEnabled() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN double tap is enabled, prox not covered whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true) @@ -119,15 +119,27 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isFalseDoubleTap).thenReturn(false) // WHEN there's a double tap - underTest.onDoubleTap(downEv) + underTest.onDoubleTapEvent(upEv) // THEN wake up device if dozing verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString()) } @Test + fun testGestureDetector_doubleTapEnabled_onDownEvent_noFalsingCheck() { + // GIVEN tap is enabled + whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) + + // WHEN there's a double tap on DOWN event + underTest.onDoubleTapEvent(downEv) + + // THEN don't check the falsing manager, should only be checked on the UP event + verify(falsingManager, never()).isFalseDoubleTap() + } + + @Test fun testGestureDetector_singleTapEnabled_falsing() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN tap is enabled, prox not covered whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) @@ -138,29 +150,43 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true) // WHEN there's a tap - underTest.onSingleTapConfirmed(downEv) + underTest.onSingleTapUp(upEv) // THEN the device doesn't wake up verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) } @Test - fun testGestureDetector_notPulsing_noFalsingCheck() { - whenever(statusBarStateController.isPulsing).thenReturn(false) + fun testSingleTap_notDozing_noFalsingCheck() { + whenever(statusBarStateController.isDozing).thenReturn(false) - // GIVEN tap is enabled, prox not covered + // GIVEN tap is enabled whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) // WHEN there's a tap - underTest.onSingleTapConfirmed(downEv) + underTest.onSingleTapUp(upEv) - // THEN the falsing manager never gets a call (because the device wasn't pulsing + // THEN the falsing manager never gets a call (because the device wasn't dozing + // during the tap) + verify(falsingManager, never()).isFalseTap(anyInt()) + } + + @Test + fun testDoubleTap_notDozing_noFalsingCheck() { + whenever(statusBarStateController.isDozing).thenReturn(false) + + // GIVEN tap is enabled + whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) + // WHEN there's a tap + underTest.onDoubleTapEvent(upEv) + + // THEN the falsing manager never gets a call (because the device wasn't dozing // during the tap) verify(falsingManager, never()).isFalseTap(anyInt()) } @Test fun testGestureDetector_doubleTapEnabled_falsing() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN double tap is enabled, prox not covered whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true) @@ -170,8 +196,8 @@ class PulsingGestureListenerTest : SysuiTestCase() { // GIVEN the falsing manager thinks the tap is a false tap whenever(falsingManager.isFalseDoubleTap).thenReturn(true) - // WHEN there's a tap - underTest.onDoubleTap(downEv) + // WHEN there's a double tap ACTION_UP event + underTest.onDoubleTapEvent(upEv) // THEN the device doesn't wake up verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) @@ -179,7 +205,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { @Test fun testGestureDetector_singleTapEnabled_proxCovered() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN tap is enabled, not a false tap based on classifiers whenever(ambientDisplayConfiguration.tapGestureEnabled(anyInt())).thenReturn(true) @@ -190,7 +216,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isProximityNear()).thenReturn(true) // WHEN there's a tap - underTest.onSingleTapConfirmed(downEv) + underTest.onSingleTapUp(upEv) // THEN the device doesn't wake up verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) @@ -198,7 +224,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { @Test fun testGestureDetector_doubleTapEnabled_proxCovered() { - whenever(statusBarStateController.isPulsing).thenReturn(true) + whenever(statusBarStateController.isDozing).thenReturn(true) // GIVEN double tap is enabled, not a false tap based on classifiers whenever(ambientDisplayConfiguration.doubleTapGestureEnabled(anyInt())).thenReturn(true) @@ -209,7 +235,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { whenever(falsingManager.isProximityNear()).thenReturn(true) // WHEN there's a tap - underTest.onDoubleTap(downEv) + underTest.onDoubleTapEvent(upEv) // THEN the device doesn't wake up verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString()) @@ -227,3 +253,4 @@ class PulsingGestureListenerTest : SysuiTestCase() { } private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) +private val upEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt new file mode 100644 index 000000000000..eb34561d15a0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt @@ -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 com.android.systemui.shared.clocks + +import android.testing.AndroidTestingRunner +import android.view.LayoutInflater +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.TextAnimator +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.junit.MockitoJUnit + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class AnimatableClockViewTest : SysuiTestCase() { + + @JvmField @Rule val mockito = MockitoJUnit.rule() + + @Mock private lateinit var mockTextAnimator: TextAnimator + private lateinit var clockView: AnimatableClockView + + @Before + fun setUp() { + val layoutInflater = LayoutInflater.from(context) + clockView = + layoutInflater.inflate(R.layout.clock_default_small, null) as AnimatableClockView + clockView.textAnimatorFactory = { _, _ -> mockTextAnimator } + } + + @Test + fun validateColorAnimationRunsBeforeMeasure() { + clockView.setColors(100, 200) + clockView.animateAppearOnLockscreen() + clockView.measure(50, 50) + + verify(mockTextAnimator).glyphFilter = null + verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, false, 350L, null, 0L, null) + verifyNoMoreInteractions(mockTextAnimator) + } + + @Test + fun validateColorAnimationRunsAfterMeasure() { + clockView.setColors(100, 200) + clockView.measure(50, 50) + clockView.animateAppearOnLockscreen() + + verify(mockTextAnimator, times(2)).glyphFilter = null + verify(mockTextAnimator).setTextStyle(100, -1.0f, 200, false, 0L, null, 0L, null) + verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, true, 350L, null, 0L, null) + verifyNoMoreInteractions(mockTextAnimator) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index e790d85763d0..a4453f854ce5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -505,4 +506,21 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mBouncerExpansionCallback.onVisibilityChanged(false); verify(mCentralSurfaces).setBouncerShowingOverDream(false); } + + @Test + public void testSetDozing_Dozing() { + clearInvocations(mBouncer); + mStatusBarKeyguardViewManager.onDozingChanged(true); + // Once when shown and once with dozing changed. + verify(mBouncer, times(1)).hide(false); + } + + @Test + public void testSetDozing_notDozing() { + mStatusBarKeyguardViewManager.onDozingChanged(true); + clearInvocations(mBouncer); + mStatusBarKeyguardViewManager.onDozingChanged(false); + // Once when shown and twice with dozing changed. + verify(mBouncer, times(1)).hide(false); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index 3dd36d134cf7..d0391ac0795c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -41,6 +41,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.systemui.SysuiTestCase; +import com.android.systemui.bluetooth.BluetoothLogger; import com.android.systemui.dump.DumpManager; import org.junit.Before; @@ -81,6 +82,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl = new BluetoothControllerImpl(mContext, mMockDumpManager, + mock(BluetoothLogger.class), mTestableLooper.getLooper(), mTestableLooper.getLooper(), mMockBluetoothManager); @@ -233,4 +235,11 @@ public class BluetoothControllerImplTest extends SysuiTestCase { assertTrue(mBluetoothControllerImpl.isBluetoothAudioActive()); assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()); } + + /** Regression test for b/246876230. */ + @Test + public void testOnActiveDeviceChanged_null_noCrash() { + mBluetoothControllerImpl.onActiveDeviceChanged(null, BluetoothProfile.HEADSET); + // No assert, just need no crash. + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index f55cfb1ab210..8d94f9545b59 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1113,7 +1113,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final List<AccessibilityServiceInfo> result = new ArrayList<>(serviceCount); for (int i = 0; i < serviceCount; ++i) { final AccessibilityServiceConnection service = services.get(i); - if ((service.mFeedbackType & feedbackType) != 0) { + if ((service.mFeedbackType & feedbackType) != 0 + || feedbackType == AccessibilityServiceInfo.FEEDBACK_ALL_MASK) { result.add(service.getServiceInfo()); } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java index 93cc611c803f..82628a6c0b72 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java @@ -46,6 +46,9 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe private static final String TAG = "CDM_CompanionServiceConnector"; private static final boolean DEBUG = false; + /* Unbinding before executing the callbacks can cause problems. Wait 5-seconds before unbind. */ + private static final long UNBIND_POST_DELAY_MS = 5_000; + /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector} */ interface Listener { void onBindingDied(@UserIdInt int userId, @NonNull String packageName, @@ -110,9 +113,15 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe * IMPORTANT: use this method instead of invoking {@link ServiceConnector#unbind()} directly, * because the latter may cause previously posted callback, such as * {@link ICompanionDeviceService#onDeviceDisappeared(AssociationInfo)} to be dropped. + * + * {@link ICompanionDeviceService} is a non-blocking interface and doesn't wait for job + * completion, which makes {@link ServiceConnector#post(VoidJob)} obsolete for ensuring the + * order of execution. Give 5 seconds for all the callbacks to finish before unbinding. They + * may or may not have finished executing, but we shouldn't let user-overridden methods block + * the service from unbinding indefinitely. */ void postUnbind() { - post(it -> unbind()); + getJobHandler().postDelayed(this::unbind, UNBIND_POST_DELAY_MS); } boolean isPrimary() { diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index 05e85e367be8..838cbd9079d6 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -61,10 +61,12 @@ class InputController { private static final AtomicLong sNextPhysId = new AtomicLong(1); + static final String PHYS_TYPE_DPAD = "Dpad"; static final String PHYS_TYPE_KEYBOARD = "Keyboard"; static final String PHYS_TYPE_MOUSE = "Mouse"; static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen"; @StringDef(prefix = { "PHYS_TYPE_" }, value = { + PHYS_TYPE_DPAD, PHYS_TYPE_KEYBOARD, PHYS_TYPE_MOUSE, PHYS_TYPE_TOUCHSCREEN, @@ -121,6 +123,22 @@ class InputController { } } + void createDpad(@NonNull String deviceName, + int vendorId, + int productId, + @NonNull IBinder deviceToken, + int displayId) { + final String phys = createPhys(PHYS_TYPE_DPAD); + try { + createDeviceInternal(InputDeviceDescriptor.TYPE_DPAD, deviceName, vendorId, + productId, deviceToken, displayId, phys, + () -> mNativeWrapper.openUinputDpad(deviceName, vendorId, productId, phys)); + } catch (DeviceCreationException e) { + throw new RuntimeException( + "Failed to create virtual dpad device '" + deviceName + "'.", e); + } + } + void createKeyboard(@NonNull String deviceName, int vendorId, int productId, @@ -253,6 +271,19 @@ class InputController { InputManager.getInstance().addUniqueIdAssociation(phys, displayUniqueId); } + boolean sendDpadKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) { + synchronized (mLock) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { + throw new IllegalArgumentException( + "Could not send key event to input device for given token"); + } + return mNativeWrapper.writeDpadKeyEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getKeyCode(), event.getAction()); + } + } + boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) { synchronized (mLock) { final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( @@ -366,6 +397,8 @@ class InputController { } } + private static native int nativeOpenUinputDpad(String deviceName, int vendorId, + int productId, String phys); private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId, int productId, String phys); private static native int nativeOpenUinputMouse(String deviceName, int vendorId, int productId, @@ -373,6 +406,7 @@ class InputController { private static native int nativeOpenUinputTouchscreen(String deviceName, int vendorId, int productId, String phys, int height, int width); private static native boolean nativeCloseUinput(int fd); + private static native boolean nativeWriteDpadKeyEvent(int fd, int androidKeyCode, int action); private static native boolean nativeWriteKeyEvent(int fd, int androidKeyCode, int action); private static native boolean nativeWriteButtonEvent(int fd, int buttonCode, int action); private static native boolean nativeWriteTouchEvent(int fd, int pointerId, int toolType, @@ -385,6 +419,10 @@ class InputController { /** Wrapper around the static native methods for tests. */ @VisibleForTesting protected static class NativeWrapper { + public int openUinputDpad(String deviceName, int vendorId, int productId, String phys) { + return nativeOpenUinputDpad(deviceName, vendorId, productId, phys); + } + public int openUinputKeyboard(String deviceName, int vendorId, int productId, String phys) { return nativeOpenUinputKeyboard(deviceName, vendorId, productId, phys); } @@ -403,6 +441,10 @@ class InputController { return nativeCloseUinput(fd); } + public boolean writeDpadKeyEvent(int fd, int androidKeyCode, int action) { + return nativeWriteDpadKeyEvent(fd, androidKeyCode, action); + } + public boolean writeKeyEvent(int fd, int androidKeyCode, int action) { return nativeWriteKeyEvent(fd, androidKeyCode, action); } @@ -433,10 +475,12 @@ class InputController { static final int TYPE_KEYBOARD = 1; static final int TYPE_MOUSE = 2; static final int TYPE_TOUCHSCREEN = 3; + static final int TYPE_DPAD = 4; @IntDef(prefix = { "TYPE_" }, value = { TYPE_KEYBOARD, TYPE_MOUSE, TYPE_TOUCHSCREEN, + TYPE_DPAD, }) @Retention(RetentionPolicy.SOURCE) @interface Type { diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index cca3212703f0..4204162f3d98 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -344,6 +344,33 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @Override // Binder call + public void createVirtualDpad( + int displayId, + @NonNull String deviceName, + int vendorId, + int productId, + @NonNull IBinder deviceToken) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CREATE_VIRTUAL_DEVICE, + "Permission required to create a virtual dpad"); + synchronized (mVirtualDeviceLock) { + if (!mVirtualDisplayIds.contains(displayId)) { + throw new SecurityException( + "Cannot create a virtual dpad for a display not associated with " + + "this virtual device"); + } + } + final long token = Binder.clearCallingIdentity(); + try { + mInputController.createDpad(deviceName, vendorId, productId, deviceToken, + displayId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override // Binder call public void createVirtualKeyboard( int displayId, @@ -437,6 +464,16 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } @Override // Binder call + public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) { + final long binderToken = Binder.clearCallingIdentity(); + try { + return mInputController.sendDpadKeyEvent(token, event); + } finally { + Binder.restoreCallingIdentity(binderToken); + } + } + + @Override // Binder call public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) { final long binderToken = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 86c97707362a..a97173d2695f 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -29,6 +29,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU; import static com.android.server.am.ActivityManagerService.TAG_MU; import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_PROVIDER; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -48,7 +49,6 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; import android.content.pm.UserInfo; @@ -960,20 +960,22 @@ public class ContentProviderHelper { String getProviderMimeType(Uri uri, int userId) { mService.enforceNotIsolatedCaller("getProviderMimeType"); final String name = uri.getAuthority(); - int callingUid = Binder.getCallingUid(); - int callingPid = Binder.getCallingPid(); - long ident = 0; - boolean clearedIdentity = false; - userId = mService.mUserController.unsafeConvertIncomingUser(userId); - if (canClearIdentity(callingPid, callingUid, userId)) { - clearedIdentity = true; - ident = Binder.clearCallingIdentity(); + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int safeUserId = mService.mUserController.unsafeConvertIncomingUser(userId); + final long ident = canClearIdentity(callingPid, callingUid, safeUserId) + ? Binder.clearCallingIdentity() : 0; + final ContentProviderHolder holder; + try { + holder = getContentProviderExternalUnchecked(name, null /* token */, callingUid, + "*getmimetype*", safeUserId); + } finally { + if (ident != 0) { + Binder.restoreCallingIdentity(ident); + } } - ContentProviderHolder holder = null; try { - holder = getContentProviderExternalUnchecked(name, null, callingUid, - "*getmimetype*", userId); - if (holder != null) { + if (isHolderVisibleToCaller(holder, callingUid, safeUserId)) { final IBinder providerConnection = holder.connection; final ComponentName providerName = holder.info.getComponentName(); // Note: creating a new Runnable instead of using a lambda here since lambdas in @@ -992,6 +994,13 @@ public class ContentProviderHelper { return holder.provider.getType(uri); } finally { mService.mHandler.removeCallbacks(providerNotResponding); + // We need to clear the identity to call removeContentProviderExternalUnchecked + final long token = Binder.clearCallingIdentity(); + try { + removeContentProviderExternalUnchecked(name, null /* token */, safeUserId); + } finally { + Binder.restoreCallingIdentity(token); + } } } } catch (RemoteException e) { @@ -1000,18 +1009,6 @@ public class ContentProviderHelper { } catch (Exception e) { Log.w(TAG, "Exception while determining type of " + uri, e); return null; - } finally { - // We need to clear the identity to call removeContentProviderExternalUnchecked - if (!clearedIdentity) { - ident = Binder.clearCallingIdentity(); - } - try { - if (holder != null) { - removeContentProviderExternalUnchecked(name, null, userId); - } - } finally { - Binder.restoreCallingIdentity(ident); - } } return null; @@ -1029,12 +1026,20 @@ public class ContentProviderHelper { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int safeUserId = mService.mUserController.unsafeConvertIncomingUser(userId); - final long ident = canClearIdentity(callingPid, callingUid, userId) + final long ident = canClearIdentity(callingPid, callingUid, safeUserId) ? Binder.clearCallingIdentity() : 0; + final ContentProviderHolder holder; try { - final ContentProviderHolder holder = getContentProviderExternalUnchecked(name, null, - callingUid, "*getmimetype*", safeUserId); - if (holder != null) { + holder = getContentProviderExternalUnchecked(name, null /* token */, callingUid, + "*getmimetype*", safeUserId); + } finally { + if (ident != 0) { + Binder.restoreCallingIdentity(ident); + } + } + + try { + if (isHolderVisibleToCaller(holder, callingUid, safeUserId)) { holder.provider.getTypeAsync(uri, new RemoteCallback(result -> { final long identity = Binder.clearCallingIdentity(); try { @@ -1050,8 +1055,6 @@ public class ContentProviderHelper { } catch (RemoteException e) { Log.w(TAG, "Content provider dead retrieving " + uri, e); resultCallback.sendResult(Bundle.EMPTY); - } finally { - Binder.restoreCallingIdentity(ident); } } @@ -1067,6 +1070,35 @@ public class ContentProviderHelper { callingUid, -1, true) == PackageManager.PERMISSION_GRANTED; } + private boolean isHolderVisibleToCaller(@Nullable ContentProviderHolder holder, int callingUid, + @UserIdInt int userId) { + if (holder == null || holder.info == null) { + return false; + } + + if (isAuthorityRedirectedForCloneProfile(holder.info.authority) + && resolveParentUserIdForCloneProfile(userId) != userId) { + // Since clone profile shares certain providers with its parent and the access is + // re-directed as well, the holder may not actually be installed on the clone profile. + return !mService.getPackageManagerInternal().filterAppAccess(holder.info.packageName, + callingUid, userId, false /* filterUninstalled */); + } + + return !mService.getPackageManagerInternal().filterAppAccess(holder.info.packageName, + callingUid, userId); + } + + private static @UserIdInt int resolveParentUserIdForCloneProfile(@UserIdInt int userId) { + final UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class); + final UserInfo userInfo = umInternal.getUserInfo(userId); + + if (userInfo == null || !userInfo.isCloneProfile()) { + return userId; + } + + return umInternal.getProfileParentId(userId); + } + /** * Check if the calling UID has a possible chance at accessing the provider * at the given authority and user. @@ -1135,9 +1167,7 @@ public class ContentProviderHelper { "*checkContentProviderUriPermission*", userId); if (holder != null) { - final PackageManagerInternal packageManagerInt = LocalServices.getService( - PackageManagerInternal.class); - final AndroidPackage androidPackage = packageManagerInt + final AndroidPackage androidPackage = mService.getPackageManagerInternal() .getPackage(Binder.getCallingUid()); if (androidPackage == null) { return PackageManager.PERMISSION_DENIED; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 4a7e63162747..af73c2bac6f9 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -4373,9 +4373,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch final PackageManager pm = mContext.getPackageManager(); final String supplementalPackageName = pm.getSdkSandboxPackageName(); if (Objects.equals(packageName, supplementalPackageName)) { - int supplementalAppId = pm.getPackageUid(supplementalPackageName, - PackageManager.PackageInfoFlags.of(0)); - uid = UserHandle.getUid(UserHandle.getUserId(uid), supplementalAppId); + uid = pm.getPackageUidAsUser(supplementalPackageName, + PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid)); } } catch (PackageManager.NameNotFoundException e) { // Shouldn't happen for the supplemental package diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 99bbc3f2d86b..32ff5e220825 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -71,6 +71,9 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { private static final String TAG = "HdmiCecLocalDeviceAudioSystem"; private static final boolean WAKE_ON_HOTPLUG = false; + private static final int MAX_CHANNELS = 8; + private static final HashMap<Integer, List<Integer>> AUDIO_CODECS_MAP = + mapAudioCodecWithAudioFormat(); // Whether the System Audio Control feature is enabled or not. True by default. @GuardedBy("mLock") @@ -485,17 +488,17 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } } - @AudioCodec int[] audioFormatCodes = parseAudioFormatCodes(message.getParams()); + @AudioCodec int[] audioCodecs = parseAudioCodecs(message.getParams()); byte[] sadBytes; if (config != null && config.size() > 0) { - sadBytes = getSupportedShortAudioDescriptorsFromConfig(config, audioFormatCodes); + sadBytes = getSupportedShortAudioDescriptorsFromConfig(config, audioCodecs); } else { AudioDeviceInfo deviceInfo = getSystemAudioDeviceInfo(); if (deviceInfo == null) { return Constants.ABORT_UNABLE_TO_DETERMINE; } - sadBytes = getSupportedShortAudioDescriptors(deviceInfo, audioFormatCodes); + sadBytes = getSupportedShortAudioDescriptors(deviceInfo, audioCodecs); } if (sadBytes.length == 0) { @@ -508,11 +511,12 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } } - private byte[] getSupportedShortAudioDescriptors( - AudioDeviceInfo deviceInfo, @AudioCodec int[] audioFormatCodes) { - ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length); - for (@AudioCodec int audioFormatCode : audioFormatCodes) { - byte[] sad = getSupportedShortAudioDescriptor(deviceInfo, audioFormatCode); + @VisibleForTesting + byte[] getSupportedShortAudioDescriptors( + AudioDeviceInfo deviceInfo, @AudioCodec int[] audioCodecs) { + ArrayList<byte[]> sads = new ArrayList<>(audioCodecs.length); + for (@AudioCodec int audioCodec : audioCodecs) { + byte[] sad = getSupportedShortAudioDescriptor(deviceInfo, audioCodec); if (sad != null) { if (sad.length == 3) { @@ -520,7 +524,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } else { HdmiLogger.warning( "Dropping Short Audio Descriptor with length %d for requested codec %x", - sad.length, audioFormatCode); + sad.length, audioCodec); } } } @@ -528,7 +532,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } private byte[] getSupportedShortAudioDescriptorsFromConfig( - List<DeviceConfig> deviceConfig, @AudioCodec int[] audioFormatCodes) { + List<DeviceConfig> deviceConfig, @AudioCodec int[] audioCodecs) { DeviceConfig deviceConfigToUse = null; String audioDeviceName = SystemProperties.get( Constants.PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT, @@ -544,13 +548,13 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { return new byte[0]; } HashMap<Integer, byte[]> map = new HashMap<>(); - ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length); + ArrayList<byte[]> sads = new ArrayList<>(audioCodecs.length); for (CodecSad codecSad : deviceConfigToUse.supportedCodecs) { map.put(codecSad.audioCodec, codecSad.sad); } - for (int i = 0; i < audioFormatCodes.length; i++) { - if (map.containsKey(audioFormatCodes[i])) { - byte[] sad = map.get(audioFormatCodes[i]); + for (int i = 0; i < audioCodecs.length; i++) { + if (map.containsKey(audioCodecs[i])) { + byte[] sad = map.get(audioCodecs[i]); if (sad != null && sad.length == 3) { sads.add(sad); } @@ -572,42 +576,171 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { /** * Returns a 3 byte short audio descriptor as described in CEC 1.4 table 29 or null if the - * audioFormatCode is not supported. + * audioCodec is not supported. */ @Nullable - private byte[] getSupportedShortAudioDescriptor( - AudioDeviceInfo deviceInfo, @AudioCodec int audioFormatCode) { - switch (audioFormatCode) { - case Constants.AUDIO_CODEC_NONE: { - return null; - } - case Constants.AUDIO_CODEC_LPCM: { - return getLpcmShortAudioDescriptor(deviceInfo); + @VisibleForTesting + byte[] getSupportedShortAudioDescriptor( + AudioDeviceInfo deviceInfo, @AudioCodec int audioCodec) { + byte[] shortAudioDescriptor = new byte[3]; + + int[] deviceSupportedAudioFormats = deviceInfo.getEncodings(); + // Return null when audioCodec or device does not support any audio formats. + if (!AUDIO_CODECS_MAP.containsKey(audioCodec) || deviceSupportedAudioFormats.length == 0) { + return null; + } + List<Integer> audioCodecSupportedAudioFormats = AUDIO_CODECS_MAP.get(audioCodec); + + for (int supportedAudioFormat : deviceSupportedAudioFormats) { + if (audioCodecSupportedAudioFormats.contains(supportedAudioFormat)) { + // Initialise the first two bytes of short audio descriptor. + shortAudioDescriptor[0] = getFirstByteOfSAD(deviceInfo, audioCodec); + shortAudioDescriptor[1] = getSecondByteOfSAD(deviceInfo); + switch (audioCodec) { + case Constants.AUDIO_CODEC_NONE: { + return null; + } + case Constants.AUDIO_CODEC_LPCM: { + if (supportedAudioFormat == AudioFormat.ENCODING_PCM_16BIT) { + shortAudioDescriptor[2] = (byte) 0x01; + } else if (supportedAudioFormat + == AudioFormat.ENCODING_PCM_24BIT_PACKED) { + shortAudioDescriptor[2] = (byte) 0x04; + } else { + // Since no bit is reserved for these audio formats in LPCM codec. + shortAudioDescriptor[2] = (byte) 0x00; + } + return shortAudioDescriptor; + } + case Constants.AUDIO_CODEC_DD: + case Constants.AUDIO_CODEC_MPEG1: + case Constants.AUDIO_CODEC_MP3: + case Constants.AUDIO_CODEC_MPEG2: + case Constants.AUDIO_CODEC_AAC: + case Constants.AUDIO_CODEC_DTS: { + shortAudioDescriptor[2] = getThirdSadByteForCodecs2Through8(deviceInfo); + return shortAudioDescriptor; + } + case Constants.AUDIO_CODEC_DDP: + case Constants.AUDIO_CODEC_DTSHD: + case Constants.AUDIO_CODEC_TRUEHD: { + // Default value is 0x0 unless defined by Audio Codec Vendor. + shortAudioDescriptor[2] = (byte) 0x00; + return shortAudioDescriptor; + } + case Constants.AUDIO_CODEC_ATRAC: + case Constants.AUDIO_CODEC_ONEBITAUDIO: + case Constants.AUDIO_CODEC_DST: + case Constants.AUDIO_CODEC_WMAPRO: + // Not supported. + default: { + return null; + } + } } - // TODO(b/80297701): implement the rest of the codecs - case Constants.AUDIO_CODEC_DD: - case Constants.AUDIO_CODEC_MPEG1: - case Constants.AUDIO_CODEC_MP3: - case Constants.AUDIO_CODEC_MPEG2: - case Constants.AUDIO_CODEC_AAC: - case Constants.AUDIO_CODEC_DTS: - case Constants.AUDIO_CODEC_ATRAC: - case Constants.AUDIO_CODEC_ONEBITAUDIO: - case Constants.AUDIO_CODEC_DDP: - case Constants.AUDIO_CODEC_DTSHD: - case Constants.AUDIO_CODEC_TRUEHD: - case Constants.AUDIO_CODEC_DST: - case Constants.AUDIO_CODEC_WMAPRO: - default: { - return null; + } + return null; + } + + private static HashMap<Integer, List<Integer>> mapAudioCodecWithAudioFormat() { + // Mapping the values of @AudioCodec audio codecs with @AudioFormat audio formats. + HashMap<Integer, List<Integer>> audioCodecsMap = new HashMap<Integer, List<Integer>>(); + + audioCodecsMap.put(Constants.AUDIO_CODEC_NONE, List.of(AudioFormat.ENCODING_DEFAULT)); + audioCodecsMap.put( + Constants.AUDIO_CODEC_LPCM, + List.of( + AudioFormat.ENCODING_PCM_8BIT, + AudioFormat.ENCODING_PCM_16BIT, + AudioFormat.ENCODING_PCM_FLOAT, + AudioFormat.ENCODING_PCM_24BIT_PACKED, + AudioFormat.ENCODING_PCM_32BIT)); + audioCodecsMap.put(Constants.AUDIO_CODEC_DD, List.of(AudioFormat.ENCODING_AC3)); + audioCodecsMap.put(Constants.AUDIO_CODEC_MPEG1, List.of(AudioFormat.ENCODING_AAC_HE_V1)); + audioCodecsMap.put(Constants.AUDIO_CODEC_MPEG2, List.of(AudioFormat.ENCODING_AAC_HE_V2)); + audioCodecsMap.put(Constants.AUDIO_CODEC_MP3, List.of(AudioFormat.ENCODING_MP3)); + audioCodecsMap.put(Constants.AUDIO_CODEC_AAC, List.of(AudioFormat.ENCODING_AAC_LC)); + audioCodecsMap.put(Constants.AUDIO_CODEC_DTS, List.of(AudioFormat.ENCODING_DTS)); + audioCodecsMap.put( + Constants.AUDIO_CODEC_DDP, + List.of(AudioFormat.ENCODING_E_AC3, AudioFormat.ENCODING_E_AC3_JOC)); + audioCodecsMap.put(Constants.AUDIO_CODEC_DTSHD, List.of(AudioFormat.ENCODING_DTS_HD)); + audioCodecsMap.put( + Constants.AUDIO_CODEC_TRUEHD, + List.of(AudioFormat.ENCODING_DOLBY_TRUEHD, AudioFormat.ENCODING_DOLBY_MAT)); + + return audioCodecsMap; + } + + private byte getFirstByteOfSAD(AudioDeviceInfo deviceInfo, @AudioCodec int audioCodec) { + byte firstByte = 0; + int maxNumberOfChannels = getMaxNumberOfChannels(deviceInfo); + + // Fill bits 0-2 of the first byte. + firstByte |= (maxNumberOfChannels - 1); + + // Fill bits 3-6 of the first byte. + firstByte |= (audioCodec << 3); + + return firstByte; + } + + private byte getSecondByteOfSAD(AudioDeviceInfo deviceInfo) { + ArrayList<Integer> samplingRates = + new ArrayList<Integer>(Arrays.asList(32, 44, 48, 88, 96, 176, 192)); + + // samplingRatesdevicesupports is guaranteed to be not null + int[] samplingRatesDeviceSupports = deviceInfo.getSampleRates(); + if (samplingRatesDeviceSupports.length == 0) { + Slog.e(TAG, "Device supports arbitrary rates"); + // Since device supports arbitrary rates, we will return 0x7f since bit 7 is reserved. + return (byte) 0x7f; + } + byte secondByte = 0; + for (int supportedSampleRate : samplingRatesDeviceSupports) { + if (samplingRates.contains(supportedSampleRate)) { + int index = samplingRates.indexOf(supportedSampleRate); + // Setting the bit of a sample rate which is being supported. + secondByte |= (1 << index); } } + + return secondByte; } - @Nullable - private byte[] getLpcmShortAudioDescriptor(AudioDeviceInfo deviceInfo) { - // TODO(b/80297701): implement - return null; + /** + * Empty array from deviceInfo.getChannelCounts() implies device supports arbitrary channel + * counts and hence we assume max channels are supported by the device. + */ + private int getMaxNumberOfChannels(AudioDeviceInfo deviceInfo) { + int maxNumberOfChannels = MAX_CHANNELS; + int[] channelCounts = deviceInfo.getChannelCounts(); + if (channelCounts.length != 0) { + maxNumberOfChannels = channelCounts[channelCounts.length - 1]; + maxNumberOfChannels = + (maxNumberOfChannels > MAX_CHANNELS ? MAX_CHANNELS : maxNumberOfChannels); + } + return maxNumberOfChannels; + } + + private byte getThirdSadByteForCodecs2Through8(AudioDeviceInfo deviceInfo) { + /* + * Here, we are assuming that max bit rate is closely equals to the max sampling rate the + * device supports. + */ + int maxSamplingRate = 0; + int[] samplingRatesDeviceSupports = deviceInfo.getSampleRates(); + if (samplingRatesDeviceSupports.length == 0) { + maxSamplingRate = 192; + } else { + for (int sampleRate : samplingRatesDeviceSupports) { + if (maxSamplingRate < sampleRate) { + maxSamplingRate = sampleRate; + } + } + } + + return (byte) (maxSamplingRate / 8); } @Nullable @@ -634,14 +767,14 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } @AudioCodec - private int[] parseAudioFormatCodes(byte[] params) { - @AudioCodec int[] audioFormatCodes = new int[params.length]; + private int[] parseAudioCodecs(byte[] params) { + @AudioCodec int[] audioCodecs = new int[params.length]; for (int i = 0; i < params.length; i++) { byte val = params[i]; - audioFormatCodes[i] = - val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE; + audioCodecs[i] = + val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE; } - return audioFormatCodes; + return audioCodecs; } @Override diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java index c57d7e79c12a..86732a12ec2c 100644 --- a/services/core/java/com/android/server/input/BatteryController.java +++ b/services/core/java/com/android/server/input/BatteryController.java @@ -18,12 +18,17 @@ package com.android.server.input; import android.annotation.BinderThread; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; +import android.hardware.BatteryState; import android.hardware.input.IInputDeviceBatteryListener; import android.hardware.input.InputManager; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UEventObserver; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -31,6 +36,8 @@ import android.util.Slog; import android.view.InputDevice; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.input.BatteryController.UEventManager.UEventListener; import java.io.PrintWriter; import java.util.Arrays; @@ -41,7 +48,7 @@ import java.util.Set; * A thread-safe component of {@link InputManagerService} responsible for managing the battery state * of input devices. */ -final class BatteryController { +final class BatteryController implements InputManager.InputDeviceListener { private static final String TAG = BatteryController.class.getSimpleName(); // To enable these logs, run: @@ -51,15 +58,35 @@ final class BatteryController { private final Object mLock = new Object(); private final Context mContext; private final NativeInputManagerService mNative; + private final Handler mHandler; + private final UEventManager mUEventManager; // Maps a pid to the registered listener record for that process. There can only be one battery // listener per process. @GuardedBy("mLock") private final ArrayMap<Integer, ListenerRecord> mListenerRecords = new ArrayMap<>(); - BatteryController(Context context, NativeInputManagerService nativeService) { + // Maps a deviceId that is being monitored to the battery state for the device. + // This must be kept in sync with {@link #mListenerRecords}. + @GuardedBy("mLock") + private final ArrayMap<Integer, MonitoredDeviceState> mMonitoredDeviceStates = new ArrayMap<>(); + + BatteryController(Context context, NativeInputManagerService nativeService, Looper looper) { + this(context, nativeService, looper, new UEventManager() {}); + } + + @VisibleForTesting + BatteryController(Context context, NativeInputManagerService nativeService, Looper looper, + UEventManager uEventManager) { mContext = context; mNative = nativeService; + mHandler = new Handler(looper); + mUEventManager = uEventManager; + } + + void systemRunning() { + Objects.requireNonNull(mContext.getSystemService(InputManager.class)) + .registerInputDeviceListener(this, mHandler); } /** @@ -96,29 +123,45 @@ final class BatteryController { + " is already monitoring deviceId " + deviceId); } + MonitoredDeviceState deviceState = mMonitoredDeviceStates.get(deviceId); + if (deviceState == null) { + // This is the first listener that is monitoring this device. + deviceState = new MonitoredDeviceState(deviceId); + mMonitoredDeviceStates.put(deviceId, deviceState); + } + if (DEBUG) { Slog.d(TAG, "Battery listener for pid " + pid + " is monitoring deviceId " + deviceId); } - notifyBatteryListener(deviceId, listenerRecord); + notifyBatteryListener(listenerRecord, deviceState); } } - private void notifyBatteryListener(int deviceId, ListenerRecord record) { - final long eventTime = SystemClock.uptimeMillis(); + private static void notifyBatteryListener(ListenerRecord listenerRecord, + MonitoredDeviceState deviceState) { try { - record.mListener.onBatteryStateChanged( - deviceId, - hasBattery(deviceId), - mNative.getBatteryStatus(deviceId), - mNative.getBatteryCapacity(deviceId) / 100.f, - eventTime); + listenerRecord.mListener.onBatteryStateChanged( + deviceState.mDeviceId, + deviceState.mHasBattery, + deviceState.mBatteryStatus, + deviceState.mBatteryCapacity, + deviceState.mLastUpdateTime); } catch (RemoteException e) { Slog.e(TAG, "Failed to notify listener", e); } } + @GuardedBy("mLock") + private void notifyAllListenersForDeviceLocked(MonitoredDeviceState deviceState) { + mListenerRecords.forEach((pid, listenerRecord) -> { + if (listenerRecord.mMonitoredDevices.contains(deviceState.mDeviceId)) { + notifyBatteryListener(listenerRecord, deviceState); + } + }); + } + private boolean hasBattery(int deviceId) { final InputDevice device = Objects.requireNonNull(mContext.getSystemService(InputManager.class)) @@ -126,6 +169,12 @@ final class BatteryController { return device != null && device.hasBattery(); } + @GuardedBy("mLock") + private MonitoredDeviceState getDeviceStateOrThrowLocked(int deviceId) { + return Objects.requireNonNull(mMonitoredDeviceStates.get(deviceId), + "Maps are out of sync: Cannot find device state for deviceId " + deviceId); + } + /** * Unregister the battery listener for the given input device and stop monitoring its battery * state. If there are no other input devices that this listener is monitoring, the listener is @@ -170,6 +219,13 @@ final class BatteryController { + pid); } + if (!hasRegisteredListenerForDeviceLocked(deviceId)) { + // There are no more listeners monitoring this device. + final MonitoredDeviceState deviceState = getDeviceStateOrThrowLocked(deviceId); + deviceState.stopMonitoring(); + mMonitoredDeviceStates.remove(deviceId); + } + if (listenerRecord.mMonitoredDevices.isEmpty()) { // There are no more devices being monitored by this listener. listenerRecord.mListener.asBinder().unlinkToDeath(listenerRecord.mDeathRecipient, 0); @@ -178,6 +234,16 @@ final class BatteryController { } } + @GuardedBy("mLock") + private boolean hasRegisteredListenerForDeviceLocked(int deviceId) { + for (int i = 0; i < mListenerRecords.size(); i++) { + if (mListenerRecords.valueAt(i).mMonitoredDevices.contains(deviceId)) { + return true; + } + } + return false; + } + private void handleListeningProcessDied(int pid) { synchronized (mLock) { final ListenerRecord listenerRecord = mListenerRecords.get(pid); @@ -194,6 +260,19 @@ final class BatteryController { } } + // Query the battery state for the device and notify all listeners if there is a change. + private void handleBatteryChangeNotification(int deviceId, long eventTime) { + synchronized (mLock) { + final MonitoredDeviceState deviceState = mMonitoredDeviceStates.get(deviceId); + if (deviceState == null) { + return; + } + if (deviceState.updateBatteryState(eventTime)) { + notifyAllListenersForDeviceLocked(deviceState); + } + } + } + void dump(PrintWriter pw, String prefix) { synchronized (mLock) { pw.println(prefix + TAG + ": " + mListenerRecords.size() @@ -211,6 +290,29 @@ final class BatteryController { } } + @VisibleForTesting + @Override + public void onInputDeviceAdded(int deviceId) {} + + @VisibleForTesting + @Override + public void onInputDeviceRemoved(int deviceId) {} + + @VisibleForTesting + @Override + public void onInputDeviceChanged(int deviceId) { + synchronized (mLock) { + final MonitoredDeviceState deviceState = mMonitoredDeviceStates.get(deviceId); + if (deviceState == null) { + return; + } + final long eventTime = SystemClock.uptimeMillis(); + if (deviceState.updateBatteryState(eventTime)) { + notifyAllListenersForDeviceLocked(deviceState); + } + } + } + // A record of a registered battery listener from one process. private class ListenerRecord { final int mPid; @@ -232,4 +334,109 @@ final class BatteryController { + ", monitored devices=" + Arrays.toString(mMonitoredDevices.toArray()); } } + + // Holds the state of an InputDevice for which battery changes are currently being monitored. + private class MonitoredDeviceState { + private final int mDeviceId; + + private long mLastUpdateTime = 0; + private boolean mHasBattery = false; + @BatteryState.BatteryStatus + private int mBatteryStatus = BatteryState.STATUS_UNKNOWN; + private float mBatteryCapacity = Float.NaN; + + @Nullable + private UEventListener mUEventListener; + + MonitoredDeviceState(int deviceId) { + mDeviceId = deviceId; + + // Load the initial battery state and start monitoring. + final long eventTime = SystemClock.uptimeMillis(); + updateBatteryState(eventTime); + } + + // Returns true if the battery state changed since the last time it was updated. + boolean updateBatteryState(long eventTime) { + mLastUpdateTime = eventTime; + + final boolean batteryPresenceChanged = mHasBattery != hasBattery(mDeviceId); + if (batteryPresenceChanged) { + mHasBattery = !mHasBattery; + if (mHasBattery) { + startMonitoring(); + } else { + stopMonitoring(); + } + } + + final int oldStatus = mBatteryStatus; + final float oldCapacity = mBatteryCapacity; + + if (mHasBattery) { + mBatteryStatus = mNative.getBatteryStatus(mDeviceId); + mBatteryCapacity = mNative.getBatteryCapacity(mDeviceId) / 100.f; + } else { + mBatteryStatus = BatteryState.STATUS_UNKNOWN; + mBatteryCapacity = Float.NaN; + } + + return batteryPresenceChanged + || mBatteryStatus != oldStatus + || mBatteryCapacity != oldCapacity; + } + + private void startMonitoring() { + final String batteryPath = mNative.getBatteryDevicePath(mDeviceId); + if (batteryPath == null) { + return; + } + mUEventListener = new UEventListener() { + @Override + void onUEvent(long eventTime) { + handleBatteryChangeNotification(mDeviceId, eventTime); + } + }; + mUEventManager.addListener(mUEventListener, "DEVPATH=" + batteryPath); + } + + // This must be called when the device is no longer being monitored. + void stopMonitoring() { + if (mUEventListener != null) { + mUEventManager.removeListener(mUEventListener); + mUEventListener = null; + } + } + } + + // An interface used to change the API of UEventObserver to a more test-friendly format. + @VisibleForTesting + interface UEventManager { + + @VisibleForTesting + abstract class UEventListener { + private final UEventObserver mObserver = new UEventObserver() { + @Override + public void onUEvent(UEvent event) { + final long eventTime = SystemClock.uptimeMillis(); + if (DEBUG) { + Slog.d(TAG, + "UEventListener: Received UEvent: " + + event + " eventTime: " + eventTime); + } + UEventListener.this.onUEvent(eventTime); + } + }; + + abstract void onUEvent(long eventTime); + } + + default void addListener(UEventListener listener, String match) { + listener.mObserver.startObserving(match); + } + + default void removeListener(UEventListener listener) { + listener.mObserver.stopObserving(); + } + } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 3def714f0c30..4da0c0df390c 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -421,7 +421,7 @@ public class InputManagerService extends IInputManager.Stub mContext = injector.getContext(); mHandler = new InputManagerHandler(injector.getLooper()); mNative = injector.getNativeService(this); - mBatteryController = new BatteryController(mContext, mNative); + mBatteryController = new BatteryController(mContext, mNative, injector.getLooper()); mUseDevInputEventForAudioJack = mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack); @@ -561,6 +561,8 @@ public class InputManagerService extends IInputManager.Stub if (mWiredAccessoryCallbacks != null) { mWiredAccessoryCallbacks.systemReady(); } + + mBatteryController.systemRunning(); } private void reloadKeyboardLayouts() { diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 0fac808fb13b..4d55d4e545ee 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -1786,8 +1786,8 @@ abstract public class ManagedServices { * from receiving events from the profile. */ public boolean isPermittedForProfile(int userId) { - if (!mUserProfiles.canProfileUseBoundServices(userId)) { - return false; + if (!mUserProfiles.isProfileUser(userId)) { + return true; } DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(DEVICE_POLICY_SERVICE); @@ -1862,16 +1862,16 @@ abstract public class ManagedServices { } } - public boolean canProfileUseBoundServices(int userId) { + public boolean isProfileUser(int userId) { synchronized (mCurrentProfiles) { UserInfo user = mCurrentProfiles.get(userId); if (user == null) { return false; } if (user.isManagedProfile() || user.isCloneProfile()) { - return false; + return true; } - return true; + return false; } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 5d64176fce03..4b18add39999 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1829,7 +1829,7 @@ public class NotificationManagerService extends SystemService { } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); mUserProfiles.updateCache(context); - if (mUserProfiles.canProfileUseBoundServices(userId)) { + if (!mUserProfiles.isProfileUser(userId)) { // reload per-user settings mSettingsObserver.update(null); // Refresh managed services @@ -1843,7 +1843,7 @@ public class NotificationManagerService extends SystemService { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); if (userId != USER_NULL) { mUserProfiles.updateCache(context); - if (mUserProfiles.canProfileUseBoundServices(userId)) { + if (!mUserProfiles.isProfileUser(userId)) { allowDefaultApprovedServices(userId); } } @@ -1861,7 +1861,7 @@ public class NotificationManagerService extends SystemService { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); mUserProfiles.updateCache(context); mAssistants.onUserUnlocked(userId); - if (mUserProfiles.canProfileUseBoundServices(userId)) { + if (!mUserProfiles.isProfileUser(userId)) { mConditionProviders.onUserUnlocked(userId); mListeners.onUserUnlocked(userId); mZenModeHelper.onUserUnlocked(userId); diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 1c7852857bc0..baa471c297d3 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -23,7 +23,9 @@ import static android.content.Intent.ACTION_PACKAGE_CHANGED; import static android.content.Intent.ACTION_PACKAGE_REMOVED; import static android.content.Intent.ACTION_USER_ADDED; import static android.content.Intent.ACTION_USER_REMOVED; +import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.Intent.EXTRA_REASON; +import static android.content.Intent.EXTRA_USER_ID; import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED; import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_DISABLED; import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_ENABLED; @@ -39,6 +41,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.IActivityManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -56,6 +59,7 @@ import android.content.pm.overlay.OverlayPaths; import android.content.res.ApkAssets; import android.net.Uri; import android.os.Binder; +import android.os.Bundle; import android.os.Environment; import android.os.FabricatedOverlayInternal; import android.os.HandlerThread; @@ -1009,7 +1013,9 @@ public final class OverlayManagerService extends SystemService { } opti++; - if ("-h".equals(opt)) { + if ("-a".equals(opt)) { + // dumpsys will pass in -a; silently ignore it + } else if ("-h".equals(opt)) { pw.println("dump [-h] [--verbose] [--user USER_ID] [[FIELD] PACKAGE]"); pw.println(" Print debugging information about the overlay manager."); pw.println(" With optional parameter PACKAGE, limit output to the specified"); @@ -1417,20 +1423,41 @@ public final class OverlayManagerService extends SystemService { private static void broadcastActionOverlayChanged(@NonNull final Set<String> targetPackages, final int userId) { + final ActivityManagerInternal amInternal = + LocalServices.getService(ActivityManagerInternal.class); CollectionUtils.forEach(targetPackages, target -> { final Intent intent = new Intent(ACTION_OVERLAY_CHANGED, Uri.fromParts("package", target, null)); intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - try { - ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null, - null, null, android.app.AppOpsManager.OP_NONE, null, false, false, userId); - } catch (RemoteException e) { - Slog.e(TAG, "broadcastActionOverlayChanged remote exception", e); - } + intent.putExtra(EXTRA_PACKAGE_NAME, target); + intent.putExtra(EXTRA_USER_ID, userId); + amInternal.broadcastIntent(intent, null /* resultTo */, null /* requiredPermissions */, + false /* serialized */, userId, null /* appIdAllowList */, + OverlayManagerService::filterReceiverAccess, null /* bOptions */); }); } /** + * A callback from the broadcast queue to determine whether the intent + * {@link Intent#ACTION_OVERLAY_CHANGED} is visible to the receiver. + * + * @param callingUid The receiver's uid. + * @param extras The extras of intent that contains {@link Intent#EXTRA_PACKAGE_NAME} and + * {@link Intent#EXTRA_USER_ID} to check. + * @return {@code null} if the intent is not visible to the receiver. + */ + @Nullable + private static Bundle filterReceiverAccess(int callingUid, @NonNull Bundle extras) { + final String packageName = extras.getString(EXTRA_PACKAGE_NAME); + final int userId = extras.getInt(EXTRA_USER_ID); + if (LocalServices.getService(PackageManagerInternal.class).filterAppAccess( + packageName, callingUid, userId, false /* filterUninstalled */)) { + return null; + } + return extras; + } + + /** * Tell the activity manager to tell a set of packages to reload their * resources. */ diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 9f6aa633c3b2..7242a56edfba 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -271,7 +271,8 @@ final class DeletePackageHelper { // other processes clean up before deleting resources. synchronized (mPm.mInstallLock) { if (info.mArgs != null) { - mRemovePackageHelper.cleanUpResources(info.mArgs); + mRemovePackageHelper.cleanUpResources(info.mArgs.mCodeFile, + info.mArgs.mInstructionSets); } boolean reEnableStub = false; diff --git a/services/core/java/com/android/server/pm/InstallArgs.java b/services/core/java/com/android/server/pm/InstallArgs.java index 65c237804d39..a94a4e2a70be 100644 --- a/services/core/java/com/android/server/pm/InstallArgs.java +++ b/services/core/java/com/android/server/pm/InstallArgs.java @@ -70,10 +70,9 @@ final class InstallArgs { UserHandle user, String[] instructionSets, String abiOverride, String[] installGrantPermissions, List<String> allowlistedRestrictedPermissions, - int autoRevokePermissionsMode, - String traceMethod, int traceCookie, SigningDetails signingDetails, - int installReason, int installScenario, boolean forceQueryableOverride, - int dataLoaderType, int packageSource) { + int autoRevokePermissionsMode, String traceMethod, int traceCookie, + SigningDetails signingDetails, int installReason, int installScenario, + boolean forceQueryableOverride, int dataLoaderType, int packageSource) { mOriginInfo = originInfo; mMoveInfo = moveInfo; mInstallFlags = installFlags; @@ -96,18 +95,6 @@ final class InstallArgs { mPackageSource = packageSource; } - /** New install */ - InstallArgs(InstallingSession params) { - this(params.mOriginInfo, params.mMoveInfo, params.mObserver, params.mInstallFlags, - params.mInstallSource, params.mVolumeUuid, - params.getUser(), null /*instructionSets*/, params.mPackageAbiOverride, - params.mGrantedRuntimePermissions, params.mAllowlistedRestrictedPermissions, - params.mAutoRevokePermissionsMode, - params.mTraceMethod, params.mTraceCookie, params.mSigningDetails, - params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride, - params.mDataLoaderType, params.mPackageSource); - } - /** * Create args that describe an existing installed package. Typically used * when cleaning up old installs, or used as a move source. diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index d20171024cd3..a25ee5e11e7e 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -105,7 +105,6 @@ import android.content.Intent; import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.DataLoaderType; -import android.content.pm.IPackageInstallObserver2; import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageInstaller; @@ -147,6 +146,7 @@ import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.F2fsUtils; @@ -275,9 +275,9 @@ final class InstallPackageHelper { // Prune unused SharedUserSetting if (mPm.mSettings.checkAndPruneSharedUserLPw(requestSharedUserSetting, false)) { // Set the app ID in removed info for UID_REMOVED broadcasts - if (reconciledPkg.mInstallResult != null - && reconciledPkg.mInstallResult.mRemovedInfo != null) { - reconciledPkg.mInstallResult.mRemovedInfo.mRemovedAppId = + if (reconciledPkg.mInstallRequest != null + && reconciledPkg.mInstallRequest.getRemovedInfo() != null) { + reconciledPkg.mInstallRequest.getRemovedInfo().mRemovedAppId = requestSharedUserSetting.mAppId; } } @@ -307,24 +307,26 @@ final class InstallPackageHelper { mPm.mSettings.convertSharedUserSettingsLPw(sharedUserSetting); } } - if (reconciledPkg.mInstallArgs != null - && reconciledPkg.mInstallArgs.mForceQueryableOverride) { + if (reconciledPkg.mInstallRequest != null + && reconciledPkg.mInstallRequest.isForceQueryableOverride()) { pkgSetting.setForceQueryableOverride(true); } // If this is part of a standard install, set the initiating package name, else rely on // previous device state. - if (reconciledPkg.mInstallArgs != null) { - InstallSource installSource = reconciledPkg.mInstallArgs.mInstallSource; - if (installSource.initiatingPackageName != null) { - final PackageSetting ips = mPm.mSettings.getPackageLPr( - installSource.initiatingPackageName); - if (ips != null) { - installSource = installSource.setInitiatingPackageSignatures( - ips.getSignatures()); + if (reconciledPkg.mInstallRequest != null) { + InstallSource installSource = reconciledPkg.mInstallRequest.getInstallSource(); + if (installSource != null) { + if (installSource.initiatingPackageName != null) { + final PackageSetting ips = mPm.mSettings.getPackageLPr( + installSource.initiatingPackageName); + if (ips != null) { + installSource = installSource.setInitiatingPackageSignatures( + ips.getSignatures()); + } } + pkgSetting.setInstallSource(installSource); } - pkgSetting.setInstallSource(installSource); } if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) { @@ -418,8 +420,8 @@ final class InstallPackageHelper { reconciledPkg.mAllowedSharedLibraryInfos, reconciledPkg.getCombinedAvailablePackages(), scanFlags); - if (reconciledPkg.mInstallResult != null) { - reconciledPkg.mInstallResult.mLibraryConsumers = clientLibPkgs; + if (reconciledPkg.mInstallRequest != null) { + reconciledPkg.mInstallRequest.setLibraryConsumers(clientLibPkgs); } if ((scanFlags & SCAN_BOOTING) != 0) { @@ -615,20 +617,18 @@ final class InstallPackageHelper { mPm.updateSequenceNumberLP(pkgSetting, new int[]{ userId }); } // start async restore with no post-install since we finish install here - PackageInstalledInfo res = new PackageInstalledInfo( - PackageManager.INSTALL_SUCCEEDED); - res.mPkg = pkgSetting.getPkg(); - res.mNewUsers = new int[]{ userId }; - PostInstallData postInstallData = - new PostInstallData(null, res, () -> { + InstallRequest request = new InstallRequest(userId, + PackageManager.INSTALL_SUCCEEDED, pkgSetting.getPkg(), new int[]{ userId }, + () -> { mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName, userId); if (intentSender != null) { - onRestoreComplete(res.mReturnCode, mContext, intentSender); + onRestoreComplete(PackageManager.INSTALL_SUCCEEDED, mContext, + intentSender); } }); - restoreAndPostInstall(userId, res, postInstallData); + restoreAndPostInstall(request); } } finally { Binder.restoreCallingIdentity(callingId); @@ -647,18 +647,17 @@ final class InstallPackageHelper { } } - /** @param data Post-install is performed only if this is non-null. */ - public void restoreAndPostInstall( - int userId, PackageInstalledInfo res, @Nullable PostInstallData data) { + public void restoreAndPostInstall(InstallRequest request) { + final int userId = request.getUserId(); if (DEBUG_INSTALL) { - Log.v(TAG, "restoreAndPostInstall userId=" + userId + " package=" + res.mPkg); + Log.v(TAG, + "restoreAndPostInstall userId=" + userId + " package=" + request.getPkg()); } // A restore should be requested at this point if (a) the install // succeeded, (b) the operation is not an update. - final boolean update = res.mRemovedInfo != null - && res.mRemovedInfo.mRemovedPackage != null; - boolean doRestore = !update && res.mPkg != null; + final boolean update = request.isUpdate(); + boolean doRestore = !update && request.getPkg() != null; // Set up the post-install work request bookkeeping. This will be used // and cleaned up by the post-install event handling regardless of whether @@ -666,23 +665,17 @@ final class InstallPackageHelper { int token; if (mPm.mNextInstallToken < 0) mPm.mNextInstallToken = 1; token = mPm.mNextInstallToken++; - if (data != null) { - mPm.mRunningInstalls.put(token, data); - } else if (DEBUG_INSTALL) { - Log.v(TAG, "No post-install required for " + token); - } + mPm.mRunningInstalls.put(token, request); if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token); - if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) { + if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED && doRestore) { // Pass responsibility to the Backup Manager. It will perform a // restore if appropriate, then pass responsibility back to the // Package Manager to run the post-install observer callbacks // and broadcasts. - if (res.mFreezer != null) { - res.mFreezer.close(); - } - doRestore = performBackupManagerRestore(userId, token, res); + request.closeFreezer(); + doRestore = performBackupManagerRestore(userId, token, request); } // If this is an update to a package that might be potentially downgraded, then we @@ -690,8 +683,8 @@ final class InstallPackageHelper { // need to be snapshotted or restored for the package. // // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL. - if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) { - doRestore = performRollbackManagerRestore(userId, token, res, data); + if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) { + doRestore = performRollbackManagerRestore(userId, token, request); } if (!doRestore) { @@ -707,10 +700,10 @@ final class InstallPackageHelper { } /** - * Perform Backup Manager restore for a given {@link PackageInstalledInfo}. + * Perform Backup Manager restore for a given {@link InstallRequest}. * Returns whether the restore successfully completed. */ - private boolean performBackupManagerRestore(int userId, int token, PackageInstalledInfo res) { + private boolean performBackupManagerRestore(int userId, int token, InstallRequest request) { IBackupManager iBackupManager = mInjector.getIBackupManager(); if (iBackupManager != null) { // For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM @@ -725,7 +718,7 @@ final class InstallPackageHelper { try { if (iBackupManager.isUserReadyForBackup(userId)) { iBackupManager.restoreAtInstallForUser( - userId, res.mPkg.getPackageName(), token); + userId, request.getPkg().getPackageName(), token); } else { Slog.w(TAG, "User " + userId + " is not ready. Restore at install " + "didn't take place."); @@ -745,12 +738,11 @@ final class InstallPackageHelper { } /** - * Perform Rollback Manager restore for a given {@link PackageInstalledInfo}. + * Perform Rollback Manager restore for a given {@link InstallRequest}. * Returns whether the restore successfully completed. */ - private boolean performRollbackManagerRestore(int userId, int token, PackageInstalledInfo res, - PostInstallData data) { - final String packageName = res.mPkg.getPackageName(); + private boolean performRollbackManagerRestore(int userId, int token, InstallRequest request) { + final String packageName = request.getPkg().getPackageName(); final int[] allUsers = mPm.mUserManager.getUserIds(); final int[] installedUsers; @@ -762,19 +754,20 @@ final class InstallPackageHelper { if (ps != null) { appId = ps.getAppId(); ceDataInode = ps.getCeDataInode(userId); + // NOTE: We ignore the user specified in the InstallParam because we know this is + // an update, and hence need to restore data for all installed users. + installedUsers = ps.queryInstalledUsers(allUsers, true); + } else { + installedUsers = new int[0]; } - - // NOTE: We ignore the user specified in the InstallParam because we know this is - // an update, and hence need to restore data for all installed users. - installedUsers = ps.queryInstalledUsers(allUsers, true); } - boolean doSnapshotOrRestore = data != null && data.args != null - && ((data.args.mInstallFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0 - || (data.args.mInstallFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0); + final int installFlags = request.getInstallFlags(); + boolean doSnapshotOrRestore = ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0 + || (installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0); if (ps != null && doSnapshotOrRestore) { - final String seInfo = AndroidPackageUtils.getSeInfo(res.mPkg, ps); + final String seInfo = AndroidPackageUtils.getSeInfo(request.getPkg(), ps); final RollbackManagerInternal rollbackManager = mInjector.getLocalService(RollbackManagerInternal.class); rollbackManager.snapshotAndRestoreUserData(packageName, @@ -817,9 +810,8 @@ final class InstallPackageHelper { @GuardedBy("mPm.mInstallLock") private void installPackagesLI(List<InstallRequest> requests) { final Map<String, ScanResult> preparedScans = new ArrayMap<>(requests.size()); - final Map<String, InstallArgs> installArgs = new ArrayMap<>(requests.size()); - final Map<String, PackageInstalledInfo> installResults = new ArrayMap<>(requests.size()); final Map<String, PrepareResult> prepareResults = new ArrayMap<>(requests.size()); + final Map<String, InstallRequest> installRequests = new ArrayMap<>(requests.size()); final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size()); final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size()); boolean success = false; @@ -832,32 +824,30 @@ final class InstallPackageHelper { try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage"); prepareResult = - preparePackageLI(request.mArgs, request.mInstallResult); + preparePackageLI(request); } catch (PrepareFailure prepareFailure) { - request.mInstallResult.setError(prepareFailure.error, + request.setError(prepareFailure.error, prepareFailure.getMessage()); - request.mInstallResult.mOrigPackage = prepareFailure.mConflictingPackage; - request.mInstallResult.mOrigPermission = prepareFailure.mConflictingPermission; + request.setOriginPackage(prepareFailure.mConflictingPackage); + request.setOriginPermission(prepareFailure.mConflictingPermission); return; } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - request.mInstallResult.setReturnCode(PackageManager.INSTALL_SUCCEEDED); - request.mInstallResult.mInstallerPackageName = - request.mArgs.mInstallSource.installerPackageName; + request.setReturnCode(PackageManager.INSTALL_SUCCEEDED); + request.setInstallerPackageName(request.getSourceInstallerPackageName()); final String packageName = prepareResult.mPackageToScan.getPackageName(); prepareResults.put(packageName, prepareResult); - installResults.put(packageName, request.mInstallResult); - installArgs.put(packageName, request.mArgs); + installRequests.put(packageName, request); try { final ScanResult result = scanPackageTracedLI( prepareResult.mPackageToScan, prepareResult.mParseFlags, prepareResult.mScanFlags, System.currentTimeMillis(), - request.mArgs.mUser, request.mArgs.mAbiOverride); + request.getUser(), request.getAbiOverride()); if (null != preparedScans.put(result.mPkgSetting.getPkg().getPackageName(), result)) { - request.mInstallResult.setError( + request.setError( PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE, "Duplicate package " + result.mPkgSetting.getPkg().getPackageName() @@ -868,7 +858,7 @@ final class InstallPackageHelper { result.mRequest.mOldPkg, result.mPkgSetting.getPkg())) { // TODO: INSTALL_FAILED_UPDATE_INCOMPATIBLE is about incomptabible // signatures. Is there a better error code? - request.mInstallResult.setError( + request.setError( INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Update attempted to change value of " + PackageManager.PROPERTY_NO_APP_DATA_STORAGE); @@ -883,15 +873,15 @@ final class InstallPackageHelper { versionInfos.put(result.mPkgSetting.getPkg().getPackageName(), mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg())); } catch (PackageManagerException e) { - request.mInstallResult.setError("Scanning Failed.", e); + request.setError("Scanning Failed.", e); return; } } CommitRequest commitRequest; synchronized (mPm.mLock) { - ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs, - installResults, prepareResults, + ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, + installRequests, prepareResults, Collections.unmodifiableMap(mPm.mPackages), versionInfos); Map<String, ReconciledPackage> reconciledPackages; try { @@ -901,7 +891,7 @@ final class InstallPackageHelper { mPm.mSettings.getKeySetManagerService(), mPm.mSettings); } catch (ReconcileFailure e) { for (InstallRequest request : requests) { - request.mInstallResult.setError("Reconciliation failed...", e); + request.setError("Reconciliation failed...", e); } return; } finally { @@ -921,25 +911,24 @@ final class InstallPackageHelper { } finally { if (success) { for (InstallRequest request : requests) { - final InstallArgs args = request.mArgs; - if (args.mDataLoaderType != DataLoaderType.INCREMENTAL) { + if (request.getDataLoaderType() != DataLoaderType.INCREMENTAL) { continue; } - if (args.mSigningDetails.getSignatureSchemeVersion() != SIGNING_BLOCK_V4) { + if (request.getSignatureSchemeVersion() != SIGNING_BLOCK_V4) { continue; } // For incremental installs, we bypass the verifier prior to install. Now // that we know the package is valid, send a notice to the verifier with // the root hash of the base.apk. - final String baseCodePath = request.mInstallResult.mPkg.getBaseApkPath(); - final String[] splitCodePaths = request.mInstallResult.mPkg.getSplitCodePaths(); - final Uri originUri = Uri.fromFile(args.mOriginInfo.mResolvedFile); + final String baseCodePath = request.getPkg().getBaseApkPath(); + final String[] splitCodePaths = request.getPkg().getSplitCodePaths(); + final Uri originUri = request.getOriginUri(); final int verificationId = mPm.mPendingVerificationToken++; final String rootHashString = PackageManagerServiceUtils .buildVerificationRootHashString(baseCodePath, splitCodePaths); VerificationUtils.broadcastPackageVerified(verificationId, originUri, PackageManager.VERIFICATION_ALLOW, rootHashString, - args.mDataLoaderType, args.mUser, mContext); + request.getDataLoaderType(), request.getUser(), mContext); } } else { for (ScanResult result : preparedScans.values()) { @@ -951,11 +940,9 @@ final class InstallPackageHelper { // TODO(b/194319951): create a more descriptive reason than unknown // mark all non-failure installs as UNKNOWN so we do not treat them as success for (InstallRequest request : requests) { - if (request.mInstallResult.mFreezer != null) { - request.mInstallResult.mFreezer.close(); - } - if (request.mInstallResult.mReturnCode == PackageManager.INSTALL_SUCCEEDED) { - request.mInstallResult.mReturnCode = PackageManager.INSTALL_UNKNOWN; + request.closeFreezer(); + if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) { + request.setReturnCode(PackageManager.INSTALL_UNKNOWN); } } } @@ -980,18 +967,19 @@ final class InstallPackageHelper { } @GuardedBy("mPm.mInstallLock") - private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res) + private PrepareResult preparePackageLI(InstallRequest request) throws PrepareFailure { - final int installFlags = args.mInstallFlags; - final boolean onExternal = args.mVolumeUuid != null; + final int installFlags = request.getInstallFlags(); + final boolean onExternal = request.getVolumeUuid() != null; final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0); final boolean fullApp = ((installFlags & PackageManager.INSTALL_FULL_APP) != 0); final boolean virtualPreload = ((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0); final boolean isApex = ((installFlags & PackageManager.INSTALL_APEX) != 0); - final boolean isRollback = args.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK; + final boolean isRollback = + request.getInstallReason() == PackageManager.INSTALL_REASON_ROLLBACK; @PackageManagerService.ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE; - if (args.mMoveInfo != null) { + if (request.isMoveInstall()) { // moving a complete application; perform an initial scan on the new install location scanFlags |= SCAN_INITIAL; } @@ -1012,7 +1000,7 @@ final class InstallPackageHelper { } final File tmpPackageFile = new File( - isApex ? res.mApexInfo.modulePath : args.getCodePath()); + isApex ? request.getApexInfo().modulePath : request.getCodePath()); if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile); // Validity check @@ -1066,7 +1054,8 @@ final class InstallPackageHelper { } } - String pkgName = res.mName = parsedPackage.getPackageName(); + String pkgName = parsedPackage.getPackageName(); + request.setName(pkgName); if (parsedPackage.isTestOnly()) { if ((installFlags & PackageManager.INSTALL_ALLOW_TEST) == 0) { throw new PrepareFailure(INSTALL_FAILED_TEST_ONLY, "installPackageLI"); @@ -1074,8 +1063,8 @@ final class InstallPackageHelper { } // either use what we've been given or parse directly from the APK - if (args.mSigningDetails != SigningDetails.UNKNOWN) { - parsedPackage.setSigningDetails(args.mSigningDetails); + if (request.getSigningDetails() != SigningDetails.UNKNOWN) { + parsedPackage.setSigningDetails(request.getSigningDetails()); } else { final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails( @@ -1223,7 +1212,8 @@ final class InstallPackageHelper { if (ps.getPkg() != null) { systemApp = ps.getPkg().isSystem(); } - res.mOrigUsers = ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true); + request.setOriginUsers( + ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true)); } final int numGroups = ArrayUtils.size(parsedPackage.getPermissionGroups()); @@ -1299,7 +1289,7 @@ final class InstallPackageHelper { // it as dangerous leading to the group auto-grant. if ((perm.getProtectionLevel() & PermissionInfo.PROTECTION_MASK_BASE) == PermissionInfo.PROTECTION_DANGEROUS) { - if (bp != null && !bp.isRuntime()) { + if (!bp.isRuntime()) { Slog.w(TAG, "Package " + parsedPackage.getPackageName() + " trying to change a non-runtime permission " + perm.getName() @@ -1372,7 +1362,7 @@ final class InstallPackageHelper { } } - if (args.mMoveInfo != null) { + if (request.isMoveInstall()) { // We did an in-place move, so dex is ready to roll scanFlags |= SCAN_NO_DEX; scanFlags |= SCAN_MOVE; @@ -1380,7 +1370,7 @@ final class InstallPackageHelper { synchronized (mPm.mLock) { final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName); if (ps == null) { - res.setError(INSTALL_FAILED_INTERNAL_ERROR, + request.setError(INSTALL_FAILED_INTERNAL_ERROR, "Missing settings for moved package " + pkgName); } @@ -1403,7 +1393,7 @@ final class InstallPackageHelper { } boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null && pkgSetting.getPkgState().isUpdatedSystemApp(); - final String abiOverride = deriveAbiOverride(args.mAbiOverride); + final String abiOverride = deriveAbiOverride(request.getAbiOverride()); boolean isUpdatedSystemAppInferred = oldPackage != null && oldPackage.isSystem(); final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths> derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage, @@ -1419,7 +1409,7 @@ final class InstallPackageHelper { } if (!isApex) { - doRenameLI(args, res.mReturnCode, res.mReturnMsg, parsedPackage); + doRenameLI(request, parsedPackage); try { setUpFsVerityIfPossible(parsedPackage); @@ -1430,8 +1420,8 @@ final class InstallPackageHelper { } } else { // Use the path returned by apexd - parsedPackage.setPath(res.mApexInfo.modulePath); - parsedPackage.setBaseApkPath(res.mApexInfo.modulePath); + parsedPackage.setPath(request.getApexInfo().modulePath); + parsedPackage.setBaseApkPath(request.getApexInfo().modulePath); } final PackageFreezer freezer = @@ -1559,8 +1549,8 @@ final class InstallPackageHelper { // don't allow an upgrade from full to ephemeral if (isInstantApp) { - if (args.mUser == null - || args.mUser.getIdentifier() == UserHandle.USER_ALL) { + if (request.getUser() == null + || request.getUserId() == UserHandle.USER_ALL) { for (int currentUser : allUsers) { if (!ps.getInstantApp(currentUser)) { // can't downgrade from full to instant @@ -1571,10 +1561,10 @@ final class InstallPackageHelper { PackageManager.INSTALL_FAILED_SESSION_INVALID); } } - } else if (!ps.getInstantApp(args.mUser.getIdentifier())) { + } else if (!ps.getInstantApp(request.getUserId())) { // can't downgrade from full to instant Slog.w(TAG, "Can't replace full app with instant app: " + pkgName11 - + " for user: " + args.mUser.getIdentifier()); + + " for user: " + request.getUserId()); throw new PrepareFailure( PackageManager.INSTALL_FAILED_SESSION_INVALID); } @@ -1582,25 +1572,29 @@ final class InstallPackageHelper { } // Update what is removed - res.mRemovedInfo = new PackageRemovedInfo(mPm); - res.mRemovedInfo.mUid = oldPackage.getUid(); - res.mRemovedInfo.mRemovedPackage = oldPackage.getPackageName(); - res.mRemovedInfo.mInstallerPackageName = ps.getInstallSource().installerPackageName; - res.mRemovedInfo.mIsStaticSharedLib = + PackageRemovedInfo removedInfo = new PackageRemovedInfo(mPm); + removedInfo.mUid = oldPackage.getUid(); + removedInfo.mRemovedPackage = oldPackage.getPackageName(); + removedInfo.mInstallerPackageName = + ps.getInstallSource().installerPackageName; + removedInfo.mIsStaticSharedLib = parsedPackage.getStaticSharedLibName() != null; - res.mRemovedInfo.mIsUpdate = true; - res.mRemovedInfo.mOrigUsers = installedUsers; - res.mRemovedInfo.mInstallReasons = new SparseArray<>(installedUsers.length); + removedInfo.mIsUpdate = true; + removedInfo.mOrigUsers = installedUsers; + removedInfo.mInstallReasons = new SparseIntArray(installedUsers.length); for (int i = 0; i < installedUsers.length; i++) { final int userId = installedUsers[i]; - res.mRemovedInfo.mInstallReasons.put(userId, ps.getInstallReason(userId)); + removedInfo.mInstallReasons.put(userId, + ps.getInstallReason(userId)); } - res.mRemovedInfo.mUninstallReasons = new SparseArray<>(uninstalledUsers.length); + removedInfo.mUninstallReasons = new SparseIntArray(uninstalledUsers.length); for (int i = 0; i < uninstalledUsers.length; i++) { final int userId = uninstalledUsers[i]; - res.mRemovedInfo.mUninstallReasons.put(userId, ps.getUninstallReason(userId)); + removedInfo.mUninstallReasons.put(userId, + ps.getUninstallReason(userId)); } - res.mRemovedInfo.mIsExternal = oldPackage.isExternalStorage(); + removedInfo.mIsExternal = oldPackage.isExternalStorage(); + request.setRemovedInfo(removedInfo); sysPkg = oldPackage.isSystem(); if (sysPkg) { @@ -1625,7 +1619,7 @@ final class InstallPackageHelper { Slog.d(TAG, "replaceSystemPackageLI: new=" + parsedPackage + ", old=" + oldPackage); } - res.setReturnCode(PackageManager.INSTALL_SUCCEEDED); + request.setReturnCode(PackageManager.INSTALL_SUCCEEDED); targetParseFlags = systemParseFlags; targetScanFlags = systemScanFlags; } else { // non system replace @@ -1674,7 +1668,7 @@ final class InstallPackageHelper { oldPackage, parsedPackage, replace /* clearCodeCache */, sysPkg, ps, disabledPs); } finally { - res.mFreezer = freezer; + request.setFreezer(freezer); if (shouldCloseFreezerBeforeReturn) { freezer.close(); } @@ -1686,24 +1680,26 @@ final class InstallPackageHelper { * scanned package should be updated to reflect the rename. */ @GuardedBy("mPm.mInstallLock") - private void doRenameLI(InstallArgs args, int status, String statusMsg, + private void doRenameLI(InstallRequest request, ParsedPackage parsedPackage) throws PrepareFailure { - if (args.mMoveInfo != null) { + final int status = request.getReturnCode(); + final String statusMsg = request.getReturnMsg(); + if (request.isMoveInstall()) { if (status != PackageManager.INSTALL_SUCCEEDED) { - mRemovePackageHelper.cleanUpForMoveInstall(args.mMoveInfo.mToUuid, - args.mMoveInfo.mPackageName, args.mMoveInfo.mFromCodePath); + mRemovePackageHelper.cleanUpForMoveInstall(request.getMoveToUuid(), + request.getMovePackageName(), request.getMoveFromCodePath()); throw new PrepareFailure(status, statusMsg); } return; } // For file installations if (status != PackageManager.INSTALL_SUCCEEDED) { - mRemovePackageHelper.removeCodePath(args.mCodeFile); + mRemovePackageHelper.removeCodePath(request.getCodeFile()); throw new PrepareFailure(status, statusMsg); } - final File targetDir = resolveTargetDir(args); - final File beforeCodeFile = args.mCodeFile; + final File targetDir = resolveTargetDir(request.getInstallFlags(), request.getCodeFile()); + final File beforeCodeFile = request.getCodeFile(); final File afterCodeFile = PackageManagerServiceUtils.getNextCodePath(targetDir, parsedPackage.getPackageName()); @@ -1732,7 +1728,7 @@ final class InstallPackageHelper { } // Reflect the rename internally - args.mCodeFile = afterCodeFile; + request.setCodeFile(afterCodeFile); // Reflect the rename in scanned details try { @@ -1750,12 +1746,12 @@ final class InstallPackageHelper { // TODO(b/168126411): Once staged install flow starts using the same folder as non-staged // flow, we won't need this method anymore. - private File resolveTargetDir(InstallArgs args) { - boolean isStagedInstall = (args.mInstallFlags & INSTALL_STAGED) != 0; + private File resolveTargetDir(int installFlags, File codeFile) { + boolean isStagedInstall = (installFlags & INSTALL_STAGED) != 0; if (isStagedInstall) { return Environment.getDataAppDirectory(null); } else { - return args.mCodeFile.getParentFile(); + return codeFile.getParentFile(); } } @@ -1905,7 +1901,7 @@ final class InstallPackageHelper { final ScanRequest scanRequest = scanResult.mRequest; final ParsedPackage parsedPackage = scanRequest.mParsedPackage; final String packageName = parsedPackage.getPackageName(); - final PackageInstalledInfo res = reconciledPkg.mInstallResult; + final InstallRequest installRequest = reconciledPkg.mInstallRequest; final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm); final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm); @@ -1921,9 +1917,10 @@ final class InstallPackageHelper { .setFirstInstallTimeFromReplaced(deletedPkgSetting, request.mAllUsers) .setLastUpdateTime(System.currentTimeMillis()); - res.mRemovedInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList( - mPm.snapshotComputer(), reconciledPkg.mPkgSetting, request.mAllUsers, - mPm.mSettings.getPackagesLocked()); + installRequest.getRemovedInfo().mBroadcastAllowList = + mPm.mAppsFilter.getVisibilityAllowList(mPm.snapshotComputer(), + reconciledPkg.mPkgSetting, request.mAllUsers, + mPm.mSettings.getPackagesLocked()); if (reconciledPkg.mPrepareResult.mSystem) { // Remove existing system package removePackageHelper.removePackage(oldPackage, true); @@ -1931,7 +1928,7 @@ final class InstallPackageHelper { // We didn't need to disable the .apk as a current system package, // which means we are replacing another update that is already // installed. We need to make sure to delete the older one's .apk. - res.mRemovedInfo.mArgs = new InstallArgs( + installRequest.getRemovedInfo().mArgs = new InstallArgs( oldPackage.getPath(), getAppDexInstructionSets( AndroidPackageUtils.getPrimaryCpuAbi(oldPackage, @@ -1939,7 +1936,7 @@ final class InstallPackageHelper { AndroidPackageUtils.getSecondaryCpuAbi(oldPackage, deletedPkgSetting))); } else { - res.mRemovedInfo.mArgs = null; + installRequest.getRemovedInfo().mArgs = null; } } else { try { @@ -1957,7 +1954,7 @@ final class InstallPackageHelper { // Update the in-memory copy of the previous code paths. PackageSetting ps1 = mPm.mSettings.getPackageLPr( reconciledPkg.mPrepareResult.mExistingPackage.getPackageName()); - if ((reconciledPkg.mInstallArgs.mInstallFlags & PackageManager.DONT_KILL_APP) + if ((installRequest.getInstallFlags() & PackageManager.DONT_KILL_APP) == 0) { Set<String> oldCodePaths = ps1.getOldCodePaths(); if (oldCodePaths == null) { @@ -1970,12 +1967,11 @@ final class InstallPackageHelper { ps1.setOldCodePaths(null); } - if (reconciledPkg.mInstallResult.mReturnCode - == PackageManager.INSTALL_SUCCEEDED) { + if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) { PackageSetting ps2 = mPm.mSettings.getPackageLPr( parsedPackage.getPackageName()); if (ps2 != null) { - res.mRemovedInfo.mRemovedForAllUsers = + installRequest.getRemovedInfo().mRemovedForAllUsers = mPm.mPackages.get(ps2.getPackageName()) == null; } } @@ -1984,15 +1980,16 @@ final class InstallPackageHelper { AndroidPackage pkg = commitReconciledScanResultLocked( reconciledPkg, request.mAllUsers); - updateSettingsLI(pkg, reconciledPkg, request.mAllUsers, res); + updateSettingsLI(pkg, reconciledPkg, request.mAllUsers, installRequest); final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); if (ps != null) { - res.mNewUsers = ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true); + installRequest.setNewUsers( + ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true)); ps.setUpdateAvailable(false /*updateAvailable*/); } - if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED) { - mPm.updateSequenceNumberLP(ps, res.mNewUsers); + if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) { + mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers()); mPm.updateInstantAppInstallerLocked(packageName); } } @@ -2005,20 +2002,18 @@ final class InstallPackageHelper { } private void updateSettingsLI(AndroidPackage newPackage, ReconciledPackage reconciledPkg, - int[] allUsers, PackageInstalledInfo res) { - updateSettingsInternalLI(newPackage, reconciledPkg, allUsers, res); + int[] allUsers, InstallRequest installRequest) { + updateSettingsInternalLI(newPackage, reconciledPkg, allUsers, installRequest); } private void updateSettingsInternalLI(AndroidPackage pkg, ReconciledPackage reconciledPkg, - int[] allUsers, PackageInstalledInfo res) { + int[] allUsers, InstallRequest installRequest) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings"); final String pkgName = pkg.getPackageName(); - final int[] installedForUsers = res.mOrigUsers; - final InstallArgs installArgs = reconciledPkg.mInstallArgs; - final int installReason = installArgs.mInstallReason; - InstallSource installSource = installArgs.mInstallSource; - final String installerPackageName = installSource.installerPackageName; + final int[] installedForUsers = installRequest.getOriginUsers(); + final int installReason = installRequest.getInstallReason(); + final String installerPackageName = installRequest.getSourceInstallerPackageName(); if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getPath()); synchronized (mPm.mLock) { @@ -2026,15 +2021,15 @@ final class InstallPackageHelper { // of the package implies that the user actually wants to run that new code, // so we enable the package. final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName); - final int userId = installArgs.mUser.getIdentifier(); + final int userId = installRequest.getUserId(); if (ps != null) { if (pkg.isSystem()) { if (DEBUG_INSTALL) { Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName); } // Enable system package for requested users - if (res.mOrigUsers != null) { - for (int origUserId : res.mOrigUsers) { + if (installedForUsers != null) { + for (int origUserId : installedForUsers) { if (userId == UserHandle.USER_ALL || userId == origUserId) { ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, origUserId, installerPackageName); @@ -2101,20 +2096,27 @@ final class InstallPackageHelper { // When replacing an existing package, preserve the original install reason for all // users that had the package installed before. Similarly for uninstall reasons. final Set<Integer> previousUserIds = new ArraySet<>(); - if (res.mRemovedInfo != null && res.mRemovedInfo.mInstallReasons != null) { - final int installReasonCount = res.mRemovedInfo.mInstallReasons.size(); + if (installRequest.getRemovedInfo() != null + && installRequest.getRemovedInfo().mInstallReasons != null) { + final int installReasonCount = + installRequest.getRemovedInfo().mInstallReasons.size(); for (int i = 0; i < installReasonCount; i++) { - final int previousUserId = res.mRemovedInfo.mInstallReasons.keyAt(i); + final int previousUserId = + installRequest.getRemovedInfo().mInstallReasons.keyAt(i); final int previousInstallReason = - res.mRemovedInfo.mInstallReasons.valueAt(i); + installRequest.getRemovedInfo().mInstallReasons.valueAt(i); ps.setInstallReason(previousInstallReason, previousUserId); previousUserIds.add(previousUserId); } } - if (res.mRemovedInfo != null && res.mRemovedInfo.mUninstallReasons != null) { - for (int i = 0; i < res.mRemovedInfo.mUninstallReasons.size(); i++) { - final int previousUserId = res.mRemovedInfo.mUninstallReasons.keyAt(i); - final int previousReason = res.mRemovedInfo.mUninstallReasons.valueAt(i); + if (installRequest.getRemovedInfo() != null + && installRequest.getRemovedInfo().mUninstallReasons != null) { + for (int i = 0; i < installRequest.getRemovedInfo().mUninstallReasons.size(); + i++) { + final int previousUserId = + installRequest.getRemovedInfo().mUninstallReasons.keyAt(i); + final int previousReason = + installRequest.getRemovedInfo().mUninstallReasons.valueAt(i); ps.setUninstallReason(previousReason, previousUserId); } } @@ -2153,41 +2155,41 @@ final class InstallPackageHelper { final PermissionManagerServiceInternal.PackageInstalledParams.Builder permissionParamsBuilder = new PermissionManagerServiceInternal.PackageInstalledParams.Builder(); - final boolean grantPermissions = (installArgs.mInstallFlags + final boolean grantPermissions = (installRequest.getInstallFlags() & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0; if (grantPermissions) { final List<String> grantedPermissions = - installArgs.mInstallGrantPermissions != null - ? Arrays.asList(installArgs.mInstallGrantPermissions) + installRequest.getInstallGrantPermissions() != null + ? Arrays.asList(installRequest.getInstallGrantPermissions()) : pkg.getRequestedPermissions(); permissionParamsBuilder.setGrantedPermissions(grantedPermissions); } final boolean allowlistAllRestrictedPermissions = - (installArgs.mInstallFlags + (installRequest.getInstallFlags() & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) != 0; final List<String> allowlistedRestrictedPermissions = allowlistAllRestrictedPermissions ? pkg.getRequestedPermissions() - : installArgs.mAllowlistedRestrictedPermissions; + : installRequest.getAllowlistedRestrictedPermissions(); if (allowlistedRestrictedPermissions != null) { permissionParamsBuilder.setAllowlistedRestrictedPermissions( allowlistedRestrictedPermissions); } - final int autoRevokePermissionsMode = installArgs.mAutoRevokePermissionsMode; + final int autoRevokePermissionsMode = installRequest.getAutoRevokePermissionsMode(); permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode); final ScanResult scanResult = reconciledPkg.mScanResult; mPm.mPermissionManager.onPackageInstalled(pkg, scanResult.mPreviousAppId, permissionParamsBuilder.build(), userId); // Apply restricted settings on potentially dangerous packages. - if (installArgs.mPackageSource == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE - || installArgs.mPackageSource + if (installRequest.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE + || installRequest.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) { enableRestrictedSettings(pkgName, pkg.getUid()); } } - res.mName = pkgName; - res.mUid = pkg.getUid(); - res.mPkg = pkg; - res.setReturnCode(PackageManager.INSTALL_SUCCEEDED); + installRequest.setName(pkgName); + installRequest.setUid(pkg.getUid()); + installRequest.setPkg(pkg); + installRequest.setReturnCode(PackageManager.INSTALL_SUCCEEDED); //to update install status Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "writeSettings"); mPm.writeSettingsLPrTEMP(); @@ -2252,13 +2254,13 @@ final class InstallPackageHelper { // can be used for optimizations. mArtManagerService.prepareAppProfiles( pkg, - mPm.resolveUserIds(reconciledPkg.mInstallArgs.mUser.getIdentifier()), + mPm.resolveUserIds(reconciledPkg.mInstallRequest.getUserId()), /* updateReferenceProfileContent= */ true); // Compute the compilation reason from the installation scenario. final int compilationReason = mDexManager.getCompilationReasonForInstallScenario( - reconciledPkg.mInstallArgs.mInstallScenario); + reconciledPkg.mInstallRequest.getInstallScenario()); // Construct the DexoptOptions early to see if we should skip running dexopt. // @@ -2267,8 +2269,9 @@ final class InstallPackageHelper { // // Also, don't fail application installs if the dexopt step fails. final boolean isBackupOrRestore = - reconciledPkg.mInstallArgs.mInstallReason == INSTALL_REASON_DEVICE_RESTORE - || reconciledPkg.mInstallArgs.mInstallReason + reconciledPkg.mInstallRequest.getInstallReason() + == INSTALL_REASON_DEVICE_RESTORE + || reconciledPkg.mInstallRequest.getInstallReason() == INSTALL_REASON_DEVICE_SETUP; final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE @@ -2580,35 +2583,31 @@ final class InstallPackageHelper { } } - void handlePackagePostInstall(PackageInstalledInfo res, InstallArgs installArgs, - boolean launchedForRestore) { + void handlePackagePostInstall(InstallRequest request, boolean launchedForRestore) { final boolean killApp = - (installArgs.mInstallFlags & PackageManager.INSTALL_DONT_KILL_APP) == 0; + (request.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) == 0; final boolean virtualPreload = - ((installArgs.mInstallFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0); - final String installerPackage = installArgs.mInstallSource.installerPackageName; - final IPackageInstallObserver2 installObserver = installArgs.mObserver; - final int dataLoaderType = installArgs.mDataLoaderType; - final boolean succeeded = res.mReturnCode == PackageManager.INSTALL_SUCCEEDED; - final boolean update = res.mRemovedInfo != null && res.mRemovedInfo.mRemovedPackage != null; - final String packageName = res.mName; + ((request.getInstallFlags() & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0); + final String installerPackage = request.getInstallerPackageName(); + final int dataLoaderType = request.getDataLoaderType(); + final boolean succeeded = request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED; + final boolean update = request.isUpdate(); + final String packageName = request.getName(); final PackageStateInternal pkgSetting = succeeded ? mPm.snapshotComputer().getPackageStateInternal(packageName) : null; final boolean removedBeforeUpdate = (pkgSetting == null) || (pkgSetting.isSystem() && !pkgSetting.getPath().getPath().equals( - res.mPkg.getPath())); + request.getPkg().getPath())); if (succeeded && removedBeforeUpdate) { Slog.e(TAG, packageName + " was removed before handlePackagePostInstall " + "could be executed"); - res.mReturnCode = INSTALL_FAILED_PACKAGE_CHANGED; - res.mReturnMsg = "Package was removed before install could complete."; + request.setReturnCode(INSTALL_FAILED_PACKAGE_CHANGED); + request.setReturnMessage("Package was removed before install could complete."); // Remove the update failed package's older resources safely now - InstallArgs args = res.mRemovedInfo != null ? res.mRemovedInfo.mArgs : null; - if (args != null) { - mRemovePackageHelper.cleanUpResources(args); - } - mPm.notifyInstallObserver(res, installObserver); + mRemovePackageHelper.cleanUpResources(request.getOldCodeFile(), + request.getOldInstructionSet()); + mPm.notifyInstallObserver(request); return; } @@ -2617,28 +2616,31 @@ final class InstallPackageHelper { mPm.mPerUidReadTimeoutsCache = null; // Send the removed broadcasts - if (res.mRemovedInfo != null) { - if (res.mRemovedInfo.mIsExternal) { + if (request.getRemovedInfo() != null) { + if (request.getRemovedInfo().mIsExternal) { if (DEBUG_INSTALL) { - Slog.i(TAG, "upgrading pkg " + res.mRemovedInfo.mRemovedPackage + Slog.i(TAG, "upgrading pkg " + request.getRemovedInfo().mRemovedPackage + " is ASEC-hosted -> UNAVAILABLE"); } - final String[] pkgNames = new String[]{res.mRemovedInfo.mRemovedPackage}; - final int[] uids = new int[]{res.mRemovedInfo.mUid}; + final String[] pkgNames = new String[]{ + request.getRemovedInfo().mRemovedPackage}; + final int[] uids = new int[]{request.getRemovedInfo().mUid}; mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, false /* mediaStatus */, true /* replacing */, pkgNames, uids); } - res.mRemovedInfo.sendPackageRemovedBroadcasts(killApp, false /*removedBySystem*/); + request.getRemovedInfo().sendPackageRemovedBroadcasts( + killApp, false /*removedBySystem*/); } final String installerPackageName = - res.mInstallerPackageName != null - ? res.mInstallerPackageName - : res.mRemovedInfo != null - ? res.mRemovedInfo.mInstallerPackageName + request.getInstallerPackageName() != null + ? request.getInstallerPackageName() + : request.getRemovedInfo() != null + ? request.getRemovedInfo().mInstallerPackageName : null; - mPm.notifyInstantAppPackageInstalled(res.mPkg.getPackageName(), res.mNewUsers); + mPm.notifyInstantAppPackageInstalled(request.getPkg().getPackageName(), + request.getNewUsers()); // Determine the set of users who are adding this package for // the first time vs. those who are seeing an update. @@ -2646,8 +2648,9 @@ final class InstallPackageHelper { int[] firstInstantUserIds = EMPTY_INT_ARRAY; int[] updateUserIds = EMPTY_INT_ARRAY; int[] instantUserIds = EMPTY_INT_ARRAY; - final boolean allNewUsers = res.mOrigUsers == null || res.mOrigUsers.length == 0; - for (int newUser : res.mNewUsers) { + final boolean allNewUsers = request.getOriginUsers() == null + || request.getOriginUsers().length == 0; + for (int newUser : request.getNewUsers()) { final boolean isInstantApp = pkgSetting.getUserStateOrDefault(newUser) .isInstantApp(); if (allNewUsers) { @@ -2659,7 +2662,7 @@ final class InstallPackageHelper { continue; } boolean isNew = true; - for (int origUser : res.mOrigUsers) { + for (int origUser : request.getOriginUsers()) { if (origUser == newUser) { isNew = false; break; @@ -2681,20 +2684,20 @@ final class InstallPackageHelper { } // Send installed broadcasts if the package is not a static shared lib. - if (res.mPkg.getStaticSharedLibName() == null) { - mPm.mProcessLoggingHandler.invalidateBaseApkHash(res.mPkg.getBaseApkPath()); + if (request.getPkg().getStaticSharedLibName() == null) { + mPm.mProcessLoggingHandler.invalidateBaseApkHash(request.getPkg().getBaseApkPath()); // Send added for users that see the package for the first time // sendPackageAddedForNewUsers also deals with system apps - int appId = UserHandle.getAppId(res.mUid); - boolean isSystem = res.mPkg.isSystem(); + int appId = UserHandle.getAppId(request.getUid()); + boolean isSystem = request.getPkg().isSystem(); mPm.sendPackageAddedForNewUsers(mPm.snapshotComputer(), packageName, isSystem || virtualPreload, virtualPreload /*startReceiver*/, appId, firstUserIds, firstInstantUserIds, dataLoaderType); // Send added for users that don't see the package for the first time Bundle extras = new Bundle(); - extras.putInt(Intent.EXTRA_UID, res.mUid); + extras.putInt(Intent.EXTRA_UID, request.getUid()); if (update) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } @@ -2743,8 +2746,8 @@ final class InstallPackageHelper { mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, extras, 0 /*flags*/, null /*targetPackage*/, null /*finishedReceiver*/, - updateUserIds, instantUserIds, res.mRemovedInfo.mBroadcastAllowList, - null); + updateUserIds, instantUserIds, + request.getRemovedInfo().mBroadcastAllowList, null); if (installerPackageName != null) { mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, extras, 0 /*flags*/, @@ -2768,7 +2771,7 @@ final class InstallPackageHelper { null /*broadcastAllowList*/, mBroadcastHelper.getTemporaryAppAllowlistBroadcastOptions( REASON_PACKAGE_REPLACED).toBundle()); - } else if (launchedForRestore && !res.mPkg.isSystem()) { + } else if (launchedForRestore && !request.getPkg().isSystem()) { // First-install and we did a restore, so we're responsible for the // first-launch broadcast. if (DEBUG_BACKUP) { @@ -2780,17 +2783,17 @@ final class InstallPackageHelper { } // Send broadcast package appeared if external for all users - if (res.mPkg.isExternalStorage()) { + if (request.getPkg().isExternalStorage()) { if (!update) { final StorageManager storageManager = mInjector.getSystemService(StorageManager.class); VolumeInfo volume = storageManager.findVolumeByUuid( StorageManager.convert( - res.mPkg.getVolumeUuid()).toString()); + request.getPkg().getVolumeUuid()).toString()); int packageExternalStorageType = PackageManagerServiceUtils.getPackageExternalStorageType(volume, - res.mPkg.isExternalStorage()); + request.getPkg().isExternalStorage()); // If the package was installed externally, log it. if (packageExternalStorageType != StorageEnums.UNKNOWN) { FrameworkStatsLog.write( @@ -2799,19 +2802,20 @@ final class InstallPackageHelper { } } if (DEBUG_INSTALL) { - Slog.i(TAG, "upgrading pkg " + res.mPkg + " is external"); + Slog.i(TAG, "upgrading pkg " + request.getPkg() + " is external"); } final String[] pkgNames = new String[]{packageName}; - final int[] uids = new int[]{res.mPkg.getUid()}; + final int[] uids = new int[]{request.getPkg().getUid()}; mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, true /* mediaStatus */, true /* replacing */, pkgNames, uids); } - } else if (!ArrayUtils.isEmpty(res.mLibraryConsumers)) { // if static shared lib + } else if (!ArrayUtils.isEmpty(request.getLibraryConsumers())) { // if static shared lib // No need to kill consumers if it's installation of new version static shared lib. final Computer snapshot = mPm.snapshotComputer(); - final boolean dontKillApp = !update && res.mPkg.getStaticSharedLibName() != null; - for (int i = 0; i < res.mLibraryConsumers.size(); i++) { - AndroidPackage pkg = res.mLibraryConsumers.get(i); + final boolean dontKillApp = !update + && request.getPkg().getStaticSharedLibName() != null; + for (int i = 0; i < request.getLibraryConsumers().size(); i++) { + AndroidPackage pkg = request.getLibraryConsumers().get(i); // send broadcast that all consumers of the static shared library have changed mPm.sendPackageChangedBroadcast(snapshot, pkg.getPackageName(), dontKillApp, new ArrayList<>(Collections.singletonList(pkg.getPackageName())), @@ -2828,9 +2832,9 @@ final class InstallPackageHelper { } if (allNewUsers && !update) { - mPm.notifyPackageAdded(packageName, res.mUid); + mPm.notifyPackageAdded(packageName, request.getUid()); } else { - mPm.notifyPackageChanged(packageName, res.mUid); + mPm.notifyPackageChanged(packageName, request.getUid()); } // Log current value of "unknown sources" setting @@ -2838,7 +2842,8 @@ final class InstallPackageHelper { getUnknownSourcesSettings()); // Remove the replaced package's older resources safely now - InstallArgs args = res.mRemovedInfo != null ? res.mRemovedInfo.mArgs : null; + InstallArgs args = request.getRemovedInfo() != null + ? request.getRemovedInfo().mArgs : null; if (args != null) { if (!killApp) { // If we didn't kill the app, defer the deletion of code/resource files, since @@ -2847,7 +2852,7 @@ final class InstallPackageHelper { // ApplicationInfo changes have propagated to all application threads. mPm.scheduleDeferredNoKillPostDelete(args); } else { - mRemovePackageHelper.cleanUpResources(args); + mRemovePackageHelper.cleanUpResources(args.mCodeFile, args.mInstructionSets); } } else { // Force a gc to clear up things. Ask for a background one, it's fine to go on @@ -2874,21 +2879,21 @@ final class InstallPackageHelper { final boolean deferInstallObserver = succeeded && update; if (deferInstallObserver) { if (killApp) { - mPm.scheduleDeferredPendingKillInstallObserver(res, installObserver); + mPm.scheduleDeferredPendingKillInstallObserver(request); } else { - mPm.scheduleDeferredNoKillInstallObserver(res, installObserver); + mPm.scheduleDeferredNoKillInstallObserver(request); } } else { - mPm.notifyInstallObserver(res, installObserver); + mPm.notifyInstallObserver(request); } // Prune unused static shared libraries which have been cached a period of time mPm.schedulePruneUnusedStaticSharedLibraries(true /* delay */); // Log tracing if needed - if (installArgs.mTraceMethod != null) { - Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, installArgs.mTraceMethod, - installArgs.mTraceCookie); + if (request.getTraceMethod() != null) { + Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, request.getTraceMethod(), + request.getTraceCookie()); } } @@ -3936,10 +3941,10 @@ final class InstallPackageHelper { + "; " + pkgSetting.getPathString() + " --> " + parsedPackage.getPath()); - final InstallArgs args = new InstallArgs( - pkgSetting.getPathString(), getAppDexInstructionSets( - pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi())); - mRemovePackageHelper.cleanUpResources(args); + mRemovePackageHelper.cleanUpResources( + new File(pkgSetting.getPathString()), + getAppDexInstructionSets(pkgSetting.getPrimaryCpuAbi(), + pkgSetting.getSecondaryCpuAbi())); synchronized (mPm.mLock) { mPm.mSettings.enableSystemPackageLPw(pkgSetting.getPackageName()); } @@ -4021,10 +4026,9 @@ final class InstallPackageHelper { + parsedPackage.getLongVersionCode() + "; " + pkgSetting.getPathString() + " --> " + parsedPackage.getPath()); - InstallArgs args = new InstallArgs( - pkgSetting.getPathString(), getAppDexInstructionSets( - pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi())); - mRemovePackageHelper.cleanUpResources(args); + mRemovePackageHelper.cleanUpResources(new File(pkgSetting.getPathString()), + getAppDexInstructionSets( + pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi())); } else { // The application on /system is older than the application on /data. Hide // the application on /system and the version on /data will be scanned later diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 753d012a32e3..36bbf41bffe6 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -16,12 +16,389 @@ package com.android.server.pm; +import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN; +import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT; + +import static com.android.server.pm.PackageManagerService.TAG; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.apex.ApexInfo; +import android.app.AppOpsManager; +import android.content.pm.DataLoaderType; +import android.content.pm.IPackageInstallObserver2; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; +import android.content.pm.SigningDetails; +import android.net.Uri; +import android.os.UserHandle; +import android.util.ExceptionUtils; +import android.util.Slog; + +import com.android.server.pm.pkg.AndroidPackage; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + final class InstallRequest { - public final InstallArgs mArgs; - public final PackageInstalledInfo mInstallResult; + private final int mUserId; + @Nullable + private final InstallArgs mInstallArgs; + @NonNull + private final PackageInstalledInfo mInstalledInfo; + @Nullable + private Runnable mPostInstallRunnable; + @Nullable + private PackageRemovedInfo mRemovedInfo; + + // New install + InstallRequest(InstallingSession params) { + mUserId = params.getUser().getIdentifier(); + mInstallArgs = new InstallArgs(params.mOriginInfo, params.mMoveInfo, params.mObserver, + params.mInstallFlags, params.mInstallSource, params.mVolumeUuid, + params.getUser(), null /*instructionSets*/, params.mPackageAbiOverride, + params.mGrantedRuntimePermissions, params.mAllowlistedRestrictedPermissions, + params.mAutoRevokePermissionsMode, + params.mTraceMethod, params.mTraceCookie, params.mSigningDetails, + params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride, + params.mDataLoaderType, params.mPackageSource); + mInstalledInfo = new PackageInstalledInfo(); + } + + // Install existing package as user + InstallRequest(int userId, int returnCode, AndroidPackage pkg, int[] newUsers, + Runnable runnable) { + mUserId = userId; + mInstallArgs = null; + mInstalledInfo = new PackageInstalledInfo(); + mInstalledInfo.mReturnCode = returnCode; + mInstalledInfo.mPkg = pkg; + mInstalledInfo.mNewUsers = newUsers; + mPostInstallRunnable = runnable; + } + + private static class PackageInstalledInfo { + String mName; + int mUid = -1; + // The set of users that originally had this package installed. + int[] mOrigUsers; + // The set of users that now have this package installed. + int[] mNewUsers; + AndroidPackage mPkg; + int mReturnCode; + String mReturnMsg; + String mInstallerPackageName; + // The set of packages consuming this shared library or null if no consumers exist. + ArrayList<AndroidPackage> mLibraryConsumers; + PackageFreezer mFreezer; + // In some error cases we want to convey more info back to the observer + String mOrigPackage; + String mOrigPermission; + // The ApexInfo returned by ApexManager#installPackage, used by rebootless APEX install + ApexInfo mApexInfo; + } + + public String getName() { + return mInstalledInfo.mName; + } + + public String getReturnMsg() { + return mInstalledInfo.mReturnMsg; + } + + public OriginInfo getOriginInfo() { + return mInstallArgs == null ? null : mInstallArgs.mOriginInfo; + } + + public PackageRemovedInfo getRemovedInfo() { + return mRemovedInfo; + } + + public String getOrigPackage() { + return mInstalledInfo.mOrigPackage; + } + + public String getOrigPermission() { + return mInstalledInfo.mOrigPermission; + } + + @Nullable + public File getCodeFile() { + return mInstallArgs == null ? null : mInstallArgs.mCodeFile; + } + + @Nullable + public String getCodePath() { + return (mInstallArgs != null && mInstallArgs.mCodeFile != null) + ? mInstallArgs.mCodeFile.getAbsolutePath() : null; + } + + @Nullable + public String getAbiOverride() { + return mInstallArgs == null ? null : mInstallArgs.mAbiOverride; + } + + public int getReturnCode() { + return mInstalledInfo.mReturnCode; + } + + @Nullable + public IPackageInstallObserver2 getObserver() { + return mInstallArgs == null ? null : mInstallArgs.mObserver; + } + + public boolean isMoveInstall() { + return mInstallArgs != null && mInstallArgs.mMoveInfo != null; + } + + @Nullable + public String getMoveToUuid() { + return (mInstallArgs != null && mInstallArgs.mMoveInfo != null) + ? mInstallArgs.mMoveInfo.mToUuid : null; + } + + @Nullable + public String getMovePackageName() { + return (mInstallArgs != null && mInstallArgs.mMoveInfo != null) + ? mInstallArgs.mMoveInfo.mPackageName : null; + } + + @Nullable + public String getMoveFromCodePath() { + return (mInstallArgs != null && mInstallArgs.mMoveInfo != null) + ? mInstallArgs.mMoveInfo.mFromCodePath : null; + } + + @Nullable + public File getOldCodeFile() { + return (mRemovedInfo != null && mRemovedInfo.mArgs != null) + ? mRemovedInfo.mArgs.mCodeFile : null; + } + + @Nullable + public String[] getOldInstructionSet() { + return (mRemovedInfo != null && mRemovedInfo.mArgs != null) + ? mRemovedInfo.mArgs.mInstructionSets : null; + } + + public UserHandle getUser() { + return new UserHandle(mUserId); + } + + public int getUserId() { + return mUserId; + } + + public int getInstallFlags() { + return mInstallArgs == null ? 0 : mInstallArgs.mInstallFlags; + } + + public int getInstallReason() { + return mInstallArgs == null ? INSTALL_REASON_UNKNOWN : mInstallArgs.mInstallReason; + } + + @Nullable + public String getVolumeUuid() { + return mInstallArgs == null ? null : mInstallArgs.mVolumeUuid; + } + + public AndroidPackage getPkg() { + return mInstalledInfo.mPkg; + } + + @Nullable + public String getTraceMethod() { + return mInstallArgs == null ? null : mInstallArgs.mTraceMethod; + } + + public int getTraceCookie() { + return mInstallArgs == null ? 0 : mInstallArgs.mTraceCookie; + } + + public boolean isUpdate() { + return mRemovedInfo != null && mRemovedInfo.mRemovedPackage != null; + } + + @Nullable + public String getRemovedPackage() { + return mRemovedInfo != null ? mRemovedInfo.mRemovedPackage : null; + } + + public boolean isInstallForExistingUser() { + return mInstallArgs == null; + } + + @Nullable + public InstallSource getInstallSource() { + return mInstallArgs == null ? null : mInstallArgs.mInstallSource; + } + + @Nullable + public String getInstallerPackageName() { + return (mInstallArgs != null && mInstallArgs.mInstallSource != null) + ? mInstallArgs.mInstallSource.installerPackageName : null; + } + + public int getDataLoaderType() { + return mInstallArgs == null ? DataLoaderType.NONE : mInstallArgs.mDataLoaderType; + } + + public int getSignatureSchemeVersion() { + return mInstallArgs == null ? SigningDetails.SignatureSchemeVersion.UNKNOWN + : mInstallArgs.mSigningDetails.getSignatureSchemeVersion(); + } + + @NonNull + public SigningDetails getSigningDetails() { + return mInstallArgs == null ? SigningDetails.UNKNOWN : mInstallArgs.mSigningDetails; + } + + @Nullable + public Uri getOriginUri() { + return mInstallArgs == null ? null : Uri.fromFile(mInstallArgs.mOriginInfo.mResolvedFile); + } + + public ApexInfo getApexInfo() { + return mInstalledInfo.mApexInfo; + } + + public String getSourceInstallerPackageName() { + return mInstallArgs.mInstallSource.installerPackageName; + } + + public boolean isRollback() { + return mInstallArgs != null + && mInstallArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK; + } + + public int[] getNewUsers() { + return mInstalledInfo.mNewUsers; + } + + public int[] getOriginUsers() { + return mInstalledInfo.mOrigUsers; + } + + public int getUid() { + return mInstalledInfo.mUid; + } + + @Nullable + public String[] getInstallGrantPermissions() { + return mInstallArgs == null ? null : mInstallArgs.mInstallGrantPermissions; + } + + public ArrayList<AndroidPackage> getLibraryConsumers() { + return mInstalledInfo.mLibraryConsumers; + } + + @Nullable + public List<String> getAllowlistedRestrictedPermissions() { + return mInstallArgs == null ? null : mInstallArgs.mAllowlistedRestrictedPermissions; + } + + public int getAutoRevokePermissionsMode() { + return mInstallArgs == null + ? AppOpsManager.MODE_DEFAULT : mInstallArgs.mAutoRevokePermissionsMode; + } + + public int getPackageSource() { + return mInstallArgs == null + ? PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED : mInstallArgs.mPackageSource; + } + + public int getInstallScenario() { + return mInstallArgs == null ? INSTALL_SCENARIO_DEFAULT : mInstallArgs.mInstallScenario; + } + + public boolean isForceQueryableOverride() { + return mInstallArgs != null && mInstallArgs.mForceQueryableOverride; + } + + public void closeFreezer() { + if (mInstalledInfo.mFreezer != null) { + mInstalledInfo.mFreezer.close(); + } + } + + public void runPostInstallRunnable() { + if (mPostInstallRunnable != null) { + mPostInstallRunnable.run(); + } + } + + public void setCodeFile(File codeFile) { + if (mInstallArgs != null) { + mInstallArgs.mCodeFile = codeFile; + } + } + + public void setError(int code, String msg) { + setReturnCode(code); + setReturnMessage(msg); + Slog.w(TAG, msg); + } + + public void setError(String msg, PackageManagerException e) { + mInstalledInfo.mReturnCode = e.error; + setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e)); + Slog.w(TAG, msg, e); + } + + public void setReturnCode(int returnCode) { + mInstalledInfo.mReturnCode = returnCode; + } + + public void setReturnMessage(String returnMsg) { + mInstalledInfo.mReturnMsg = returnMsg; + } + + public void setApexInfo(ApexInfo apexInfo) { + mInstalledInfo.mApexInfo = apexInfo; + } + + public void setPkg(AndroidPackage pkg) { + mInstalledInfo.mPkg = pkg; + } + + public void setUid(int uid) { + mInstalledInfo.mUid = uid; + } + + public void setNewUsers(int[] newUsers) { + mInstalledInfo.mNewUsers = newUsers; + } + + public void setOriginPackage(String originPackage) { + mInstalledInfo.mOrigPackage = originPackage; + } + + public void setOriginPermission(String originPermission) { + mInstalledInfo.mOrigPermission = originPermission; + } + + public void setInstallerPackageName(String installerPackageName) { + mInstalledInfo.mInstallerPackageName = installerPackageName; + } + + public void setName(String packageName) { + mInstalledInfo.mName = packageName; + } + + public void setOriginUsers(int[] userIds) { + mInstalledInfo.mOrigUsers = userIds; + } + + public void setFreezer(PackageFreezer freezer) { + mInstalledInfo.mFreezer = freezer; + } + + public void setRemovedInfo(PackageRemovedInfo removedInfo) { + mRemovedInfo = removedInfo; + } - InstallRequest(InstallArgs args, PackageInstalledInfo res) { - mArgs = args; - mInstallResult = res; + public void setLibraryConsumers(ArrayList<AndroidPackage> libraryConsumers) { + mInstalledInfo.mLibraryConsumers = libraryConsumers; } } diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java index a7f1727dfa61..8d5a5e156e71 100644 --- a/services/core/java/com/android/server/pm/InstallingSession.java +++ b/services/core/java/com/android/server/pm/InstallingSession.java @@ -253,68 +253,69 @@ class InstallingSession { } private void processPendingInstall() { - InstallArgs args = new InstallArgs(this); + InstallRequest installRequest = new InstallRequest(this); if (mRet == PackageManager.INSTALL_SUCCEEDED) { - mRet = copyApk(args); + mRet = copyApk(installRequest); } if (mRet == PackageManager.INSTALL_SUCCEEDED) { F2fsUtils.releaseCompressedBlocks( - mPm.mContext.getContentResolver(), new File(args.getCodePath())); + mPm.mContext.getContentResolver(), new File(installRequest.getCodePath())); } + installRequest.setReturnCode(mRet); if (mParentInstallingSession != null) { - mParentInstallingSession.tryProcessInstallRequest(args, mRet); + mParentInstallingSession.tryProcessInstallRequest(installRequest); } else { - PackageInstalledInfo res = new PackageInstalledInfo(mRet); // Queue up an async operation since the package installation may take a little while. mPm.mHandler.post(() -> processInstallRequests( - res.mReturnCode == PackageManager.INSTALL_SUCCEEDED /* success */, - Collections.singletonList(new InstallRequest(args, res)))); + mRet == PackageManager.INSTALL_SUCCEEDED /* success */, + Collections.singletonList(installRequest))); } } - private int copyApk(InstallArgs args) { + private int copyApk(InstallRequest request) { if (mMoveInfo == null) { - return copyApkForFileInstall(args); + return copyApkForFileInstall(request); } else { - return copyApkForMoveInstall(args); + return copyApkForMoveInstall(request); } } - private int copyApkForFileInstall(InstallArgs args) { + private int copyApkForFileInstall(InstallRequest request) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyApk"); try { if (mOriginInfo.mStaged) { if (DEBUG_INSTALL) { Slog.d(TAG, mOriginInfo.mFile + " already staged; skipping copy"); } - args.mCodeFile = mOriginInfo.mFile; + request.setCodeFile(mOriginInfo.mFile); return PackageManager.INSTALL_SUCCEEDED; } try { final boolean isEphemeral = (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0; - args.mCodeFile = - mPm.mInstallerService.allocateStageDirLegacy(mVolumeUuid, isEphemeral); + request.setCodeFile( + mPm.mInstallerService.allocateStageDirLegacy(mVolumeUuid, isEphemeral)); } catch (IOException e) { Slog.w(TAG, "Failed to create copy file: " + e); return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; } int ret = PackageManagerServiceUtils.copyPackage( - mOriginInfo.mFile.getAbsolutePath(), args.mCodeFile); + mOriginInfo.mFile.getAbsolutePath(), request.getCodeFile()); if (ret != PackageManager.INSTALL_SUCCEEDED) { Slog.e(TAG, "Failed to copy package"); return ret; } - final boolean isIncremental = isIncrementalPath(args.mCodeFile.getAbsolutePath()); - final File libraryRoot = new File(args.mCodeFile, LIB_DIR_NAME); + final boolean isIncremental = isIncrementalPath( + request.getCodeFile().getAbsolutePath()); + final File libraryRoot = new File(request.getCodeFile(), LIB_DIR_NAME); NativeLibraryHelper.Handle handle = null; try { - handle = NativeLibraryHelper.Handle.create(args.mCodeFile); + handle = NativeLibraryHelper.Handle.create(request.getCodeFile()); ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot, - args.mAbiOverride, isIncremental); + request.getAbiOverride(), isIncremental); } catch (IOException e) { Slog.e(TAG, "Copying native libraries failed", e); ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; @@ -328,7 +329,7 @@ class InstallingSession { } } - private int copyApkForMoveInstall(InstallArgs args) { + private int copyApkForMoveInstall(InstallRequest request) { if (DEBUG_INSTALL) { Slog.d(TAG, "Moving " + mMoveInfo.mPackageName + " from " + mMoveInfo.mFromUuid + " to " + mMoveInfo.mToUuid); @@ -345,8 +346,9 @@ class InstallingSession { } final String toPathName = new File(mMoveInfo.mFromCodePath).getName(); - args.mCodeFile = new File(Environment.getDataAppDirectory(mMoveInfo.mToUuid), toPathName); - if (DEBUG_INSTALL) Slog.d(TAG, "codeFile after move is " + args.mCodeFile); + request.setCodeFile( + new File(Environment.getDataAppDirectory(mMoveInfo.mToUuid), toPathName)); + if (DEBUG_INSTALL) Slog.d(TAG, "codeFile after move is " + request.getCodeFile()); return PackageManager.INSTALL_SUCCEEDED; } @@ -460,7 +462,7 @@ class InstallingSession { List<InstallRequest> apexInstallRequests = new ArrayList<>(); List<InstallRequest> apkInstallRequests = new ArrayList<>(); for (InstallRequest request : installRequests) { - if ((request.mArgs.mInstallFlags & PackageManager.INSTALL_APEX) != 0) { + if ((request.getInstallFlags() & PackageManager.INSTALL_APEX) != 0) { apexInstallRequests.add(request); } else { apkInstallRequests.add(request); @@ -485,7 +487,7 @@ class InstallingSession { // processInstallRequestAsync. In that case just notify the observer about the // failure. InstallRequest request = apexInstallRequests.get(0); - mPm.notifyInstallObserver(request.mInstallResult, request.mArgs.mObserver); + mPm.notifyInstallObserver(request); } return; } @@ -496,28 +498,25 @@ class InstallingSession { private void processApkInstallRequests(boolean success, List<InstallRequest> installRequests) { if (success) { for (InstallRequest request : installRequests) { - if (request.mInstallResult.mReturnCode != PackageManager.INSTALL_SUCCEEDED) { - cleanUpForFailedInstall(request.mArgs); + if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) { + cleanUpForFailedInstall(request); } } mInstallPackageHelper.installPackagesTraced(installRequests); for (InstallRequest request : installRequests) { - doPostInstall(request.mInstallResult.mReturnCode, request.mArgs); + doPostInstall(request); } } for (InstallRequest request : installRequests) { - mInstallPackageHelper.restoreAndPostInstall(request.mArgs.mUser.getIdentifier(), - request.mInstallResult, - new PostInstallData(request.mArgs, - request.mInstallResult, null)); + mInstallPackageHelper.restoreAndPostInstall(request); } } - private void doPostInstall(int status, InstallArgs args) { + private void doPostInstall(InstallRequest request) { if (mMoveInfo != null) { - if (status == PackageManager.INSTALL_SUCCEEDED) { + if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) { mRemovePackageHelper.cleanUpForMoveInstall(mMoveInfo.mFromUuid, mMoveInfo.mPackageName, mMoveInfo.mFromCodePath); } else { @@ -525,18 +524,18 @@ class InstallingSession { mMoveInfo.mPackageName, mMoveInfo.mFromCodePath); } } else { - if (status != PackageManager.INSTALL_SUCCEEDED) { - mRemovePackageHelper.removeCodePath(args.mCodeFile); + if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) { + mRemovePackageHelper.removeCodePath(request.getCodeFile()); } } } - private void cleanUpForFailedInstall(InstallArgs args) { - if (args.mMoveInfo != null) { - mRemovePackageHelper.cleanUpForMoveInstall(args.mMoveInfo.mToUuid, - args.mMoveInfo.mPackageName, args.mMoveInfo.mFromCodePath); + private void cleanUpForFailedInstall(InstallRequest request) { + if (request.isMoveInstall()) { + mRemovePackageHelper.cleanUpForMoveInstall(request.getMoveToUuid(), + request.getMovePackageName(), request.getMoveFromCodePath()); } else { - mRemovePackageHelper.removeCodePath(args.mCodeFile); + mRemovePackageHelper.removeCodePath(request.getCodeFile()); } } @@ -560,7 +559,7 @@ class InstallingSession { InstallRequest request = requests.get(0); try { // Should directory scanning logic be moved to ApexManager for better test coverage? - final File dir = request.mArgs.mOriginInfo.mResolvedFile; + final File dir = request.getOriginInfo().mResolvedFile; final File[] apexes = dir.listFiles(); if (apexes == null) { throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, @@ -580,7 +579,7 @@ class InstallingSession { // The newly installed APEX will not be reverted even if // processApkInstallRequests() fails. Need a way to keep info stored in apexd // and PMS in sync in the face of install failures. - request.mInstallResult.mApexInfo = apexInfo; + request.setApexInfo(apexInfo); mPm.mHandler.post(() -> processApkInstallRequests(true, requests)); return; } else { @@ -588,10 +587,10 @@ class InstallingSession { } } } catch (PackageManagerException e) { - request.mInstallResult.setError("APEX installation failed", e); + request.setError("APEX installation failed", e); } PackageManagerService.invalidatePackageInfoCache(); - mPm.notifyInstallObserver(request.mInstallResult, request.mArgs.mObserver); + mPm.notifyInstallObserver(request); } /** @@ -600,7 +599,7 @@ class InstallingSession { */ private class MultiPackageInstallingSession { private final List<InstallingSession> mChildInstallingSessions; - private final Map<InstallArgs, Integer> mCurrentState; + private final Map<InstallRequest, Integer> mCurrentState; @NonNull final PackageManagerService mPm; final UserHandle mUser; @@ -636,8 +635,8 @@ class InstallingSession { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - public void tryProcessInstallRequest(InstallArgs args, int currentStatus) { - mCurrentState.put(args, currentStatus); + public void tryProcessInstallRequest(InstallRequest request) { + mCurrentState.put(request, request.getReturnCode()); if (mCurrentState.size() != mChildInstallingSessions.size()) { return; } @@ -651,9 +650,9 @@ class InstallingSession { } } final List<InstallRequest> installRequests = new ArrayList<>(mCurrentState.size()); - for (Map.Entry<InstallArgs, Integer> entry : mCurrentState.entrySet()) { - installRequests.add(new InstallRequest(entry.getKey(), - new PackageInstalledInfo(completeStatus))); + for (Map.Entry<InstallRequest, Integer> entry : mCurrentState.entrySet()) { + entry.getKey().setReturnCode(completeStatus); + installRequests.add(entry.getKey()); } int finalCompleteStatus = completeStatus; mPm.mHandler.post(() -> processInstallRequests( diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java index 1ea4d629eef2..4e8b0a14ae1d 100644 --- a/services/core/java/com/android/server/pm/PackageHandler.java +++ b/services/core/java/com/android/server/pm/PackageHandler.java @@ -89,18 +89,14 @@ final class PackageHandler extends Handler { case POST_INSTALL: { if (DEBUG_INSTALL) Log.v(TAG, "Handling post-install for " + msg.arg1); - PostInstallData data = mPm.mRunningInstalls.get(msg.arg1); + InstallRequest request = mPm.mRunningInstalls.get(msg.arg1); final boolean didRestore = (msg.arg2 != 0); mPm.mRunningInstalls.delete(msg.arg1); - if (data != null && data.res.mFreezer != null) { - data.res.mFreezer.close(); - } - - if (data != null && data.mPostInstallRunnable != null) { - data.mPostInstallRunnable.run(); - } else if (data != null && data.args != null) { - mInstallPackageHelper.handlePackagePostInstall(data.res, data.args, didRestore); + request.closeFreezer(); + request.runPostInstallRunnable(); + if (!request.isInstallForExistingUser()) { + mInstallPackageHelper.handlePackagePostInstall(request, didRestore); } else if (DEBUG_INSTALL) { // No post-install when we run restore from installExistingPackageForUser Slog.i(TAG, "Nothing to do for post-install token " + msg.arg1); @@ -111,7 +107,7 @@ final class PackageHandler extends Handler { case DEFERRED_NO_KILL_POST_DELETE: { InstallArgs args = (InstallArgs) msg.obj; if (args != null) { - mRemovePackageHelper.cleanUpResources(args); + mRemovePackageHelper.cleanUpResources(args.mCodeFile, args.mInstructionSets); } } break; case DEFERRED_NO_KILL_INSTALL_OBSERVER: diff --git a/services/core/java/com/android/server/pm/PackageInstalledInfo.java b/services/core/java/com/android/server/pm/PackageInstalledInfo.java deleted file mode 100644 index 247abf3b6685..000000000000 --- a/services/core/java/com/android/server/pm/PackageInstalledInfo.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.pm; - -import static com.android.server.pm.PackageManagerService.TAG; - -import android.apex.ApexInfo; -import android.util.ExceptionUtils; -import android.util.Slog; - -import com.android.server.pm.pkg.AndroidPackage; - -import java.util.ArrayList; - -final class PackageInstalledInfo { - String mName; - int mUid; - // The set of users that originally had this package installed. - int[] mOrigUsers; - // The set of users that now have this package installed. - int[] mNewUsers; - AndroidPackage mPkg; - int mReturnCode; - String mReturnMsg; - String mInstallerPackageName; - PackageRemovedInfo mRemovedInfo; - // The set of packages consuming this shared library or null if no consumers exist. - ArrayList<AndroidPackage> mLibraryConsumers; - PackageFreezer mFreezer; - - // In some error cases we want to convey more info back to the observer - String mOrigPackage; - String mOrigPermission; - - // The ApexInfo returned by ApexManager#installPackage, used by rebootless APEX install - ApexInfo mApexInfo; - - PackageInstalledInfo(int currentStatus) { - mReturnCode = currentStatus; - mUid = -1; - mPkg = null; - mRemovedInfo = null; - } - - public void setError(int code, String msg) { - setReturnCode(code); - setReturnMessage(msg); - Slog.w(TAG, msg); - } - - public void setError(String msg, PackageManagerException e) { - mReturnCode = e.error; - setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e)); - Slog.w(TAG, msg, e); - } - - public void setReturnCode(int returnCode) { - mReturnCode = returnCode; - } - - private void setReturnMessage(String returnMsg) { - mReturnMsg = returnMsg; - } -} diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 66a97b3b9a89..218d9d1fd085 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -1344,9 +1344,14 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } private String getDeviceOwnerDeletedPackageMsg() { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - return dpm.getResources().getString(PACKAGE_DELETED_BY_DO, - () -> mContext.getString(R.string.package_deleted_device_owner)); + final long ident = Binder.clearCallingIdentity(); + try { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getResources().getString(PACKAGE_DELETED_BY_DO, + () -> mContext.getString(R.string.package_deleted_device_owner)); + } finally { + Binder.restoreCallingIdentity(ident); + } } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 259202f86bc9..39a6eb7bee8d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -82,7 +82,6 @@ import android.content.pm.IDexModuleRegisterCallback; import android.content.pm.IOnChecksumsReadyListener; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver2; -import android.content.pm.IPackageInstallObserver2; import android.content.pm.IPackageLoadingProgressCallback; import android.content.pm.IPackageManager; import android.content.pm.IPackageMoveObserver; @@ -826,10 +825,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService @Watched(manual = true) private final ResolveInfo mInstantAppInstallerInfo = new ResolveInfo(); - private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>> + private final Map<String, InstallRequest> mNoKillInstallObservers = Collections.synchronizedMap(new HashMap<>()); - private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>> + private final Map<String, InstallRequest> mPendingKillInstallObservers = Collections.synchronizedMap(new HashMap<>()); // Internal interface for permission manager @@ -908,7 +907,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService // Stores a list of users whose package restrictions file needs to be updated final ArraySet<Integer> mDirtyUsers = new ArraySet<>(); - final SparseArray<PostInstallData> mRunningInstalls = new SparseArray<>(); + final SparseArray<InstallRequest> mRunningInstalls = new SparseArray<>(); int mNextInstallToken = 1; // nonzero; will be wrapped back to 1 when ++ overflows final @NonNull String[] mRequiredVerifierPackages; @@ -1155,32 +1154,30 @@ public class PackageManagerService implements PackageSender, TestUtilityService } void notifyInstallObserver(String packageName, boolean killApp) { - final Pair<PackageInstalledInfo, IPackageInstallObserver2> pair = + final InstallRequest installRequest = killApp ? mPendingKillInstallObservers.remove(packageName) : mNoKillInstallObservers.remove(packageName); - if (pair != null) { - notifyInstallObserver(pair.first, pair.second); + if (installRequest != null) { + notifyInstallObserver(installRequest); } } - void notifyInstallObserver(PackageInstalledInfo info, - IPackageInstallObserver2 installObserver) { - if (installObserver != null) { + void notifyInstallObserver(InstallRequest request) { + if (request.getObserver() != null) { try { - Bundle extras = extrasForInstallResult(info); - installObserver.onPackageInstalled(info.mName, info.mReturnCode, - info.mReturnMsg, extras); + Bundle extras = extrasForInstallResult(request); + request.getObserver().onPackageInstalled(request.getName(), + request.getReturnCode(), request.getReturnMsg(), extras); } catch (RemoteException e) { Slog.i(TAG, "Observer no longer exists."); } } } - void scheduleDeferredNoKillInstallObserver(PackageInstalledInfo info, - IPackageInstallObserver2 observer) { - String packageName = info.mPkg.getPackageName(); - mNoKillInstallObservers.put(packageName, Pair.create(info, observer)); + void scheduleDeferredNoKillInstallObserver(InstallRequest request) { + String packageName = request.getPkg().getPackageName(); + mNoKillInstallObservers.put(packageName, request); Message message = mHandler.obtainMessage(DEFERRED_NO_KILL_INSTALL_OBSERVER, packageName); mHandler.sendMessageDelayed(message, DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS); } @@ -1196,10 +1193,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService delay ? getPruneUnusedSharedLibrariesDelay() : 0); } - void scheduleDeferredPendingKillInstallObserver(PackageInstalledInfo info, - IPackageInstallObserver2 observer) { - final String packageName = info.mPkg.getPackageName(); - mPendingKillInstallObservers.put(packageName, Pair.create(info, observer)); + void scheduleDeferredPendingKillInstallObserver(InstallRequest request) { + final String packageName = request.getPkg().getPackageName(); + mPendingKillInstallObservers.put(packageName, request); final Message message = mHandler.obtainMessage(DEFERRED_PENDING_KILL_INSTALL_OBSERVER, packageName); mHandler.sendMessageDelayed(message, DEFERRED_PENDING_KILL_INSTALL_OBSERVER_DELAY_MS); @@ -1312,21 +1308,22 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } - private static Bundle extrasForInstallResult(PackageInstalledInfo res) { + private static Bundle extrasForInstallResult(InstallRequest request) { Bundle extras = null; - switch (res.mReturnCode) { + switch (request.getReturnCode()) { case PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION: { extras = new Bundle(); extras.putString(PackageManager.EXTRA_FAILURE_EXISTING_PERMISSION, - res.mOrigPermission); + request.getOrigPermission()); extras.putString(PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE, - res.mOrigPackage); + request.getOrigPackage()); break; } case PackageManager.INSTALL_SUCCEEDED: { extras = new Bundle(); extras.putBoolean(Intent.EXTRA_REPLACING, - res.mRemovedInfo != null && res.mRemovedInfo.mRemovedPackage != null); + request.getRemovedInfo() != null + && request.getRemovedInfo().mRemovedPackage != null); break; } } @@ -3111,14 +3108,14 @@ public class PackageManagerService implements PackageSender, TestUtilityService // state for observers to see the FIRST_LAUNCH signal. mHandler.post(() -> { for (int i = 0; i < mRunningInstalls.size(); i++) { - final PostInstallData data = mRunningInstalls.valueAt(i); - if (data.res.mReturnCode != PackageManager.INSTALL_SUCCEEDED) { + final InstallRequest installRequest = mRunningInstalls.valueAt(i); + if (installRequest.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) { continue; } - if (packageName.equals(data.res.mPkg.getPackageName())) { + if (packageName.equals(installRequest.getPkg().getPackageName())) { // right package; but is it for the right user? - for (int uIndex = 0; uIndex < data.res.mNewUsers.length; uIndex++) { - if (userId == data.res.mNewUsers[uIndex]) { + for (int uIndex = 0; uIndex < installRequest.getNewUsers().length; uIndex++) { + if (userId == installRequest.getNewUsers()[uIndex]) { if (DEBUG_BACKUP) { Slog.i(TAG, "Package " + packageName + " being restored so deferring FIRST_LAUNCH"); diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java index 28ad4b61d8c7..3c863d080d79 100644 --- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java +++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java @@ -28,6 +28,7 @@ import android.content.Intent; import android.os.Bundle; import android.os.PowerExemptionManager; import android.util.SparseArray; +import android.util.SparseIntArray; import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; @@ -42,8 +43,8 @@ final class PackageRemovedInfo { int[] mRemovedUsers = null; int[] mBroadcastUsers = null; int[] mInstantUserIds = null; - SparseArray<Integer> mInstallReasons; - SparseArray<Integer> mUninstallReasons; + SparseIntArray mInstallReasons; + SparseIntArray mUninstallReasons; boolean mIsRemovedPackageSystemUpdate = false; boolean mIsUpdate; boolean mDataRemoved; diff --git a/services/core/java/com/android/server/pm/PostInstallData.java b/services/core/java/com/android/server/pm/PostInstallData.java deleted file mode 100644 index 65d2a659bb45..000000000000 --- a/services/core/java/com/android/server/pm/PostInstallData.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.pm; - -import android.annotation.NonNull; -import android.annotation.Nullable; - -/** - * Record-keeping of restore-after-install operations that are currently in flight - * between the Package Manager and the Backup Manager - */ -public final class PostInstallData { - @Nullable - public final InstallArgs args; - @NonNull - public final PackageInstalledInfo res; - @Nullable - public final Runnable mPostInstallRunnable; - - PostInstallData(@Nullable InstallArgs args, @NonNull PackageInstalledInfo res, - @Nullable Runnable postInstallRunnable) { - this.args = args; - this.res = res; - mPostInstallRunnable = postInstallRunnable; - } -} diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index 0f7c6526da17..165b450ce65b 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -77,11 +77,10 @@ final class ReconcilePackageUtils { } // the following may be null if we're just reconciling on boot (and not during install) - final InstallArgs installArgs = request.mInstallArgs.get(installPackageName); - final PackageInstalledInfo res = request.mInstallResults.get(installPackageName); + final InstallRequest installRequest = request.mInstallRequests.get(installPackageName); final PrepareResult prepareResult = request.mPreparedPackages.get(installPackageName); - final boolean isInstall = installArgs != null; - if (isInstall && (res == null || prepareResult == null)) { + final boolean isInstall = installRequest != null; + if (isInstall && prepareResult == null) { throw new ReconcileFailure("Reconcile arguments are not balanced for " + installPackageName + "!"); } @@ -92,7 +91,8 @@ final class ReconcilePackageUtils { final boolean killApp = (scanResult.mRequest.mScanFlags & SCAN_DONT_KILL_APP) == 0; final int deleteFlags = PackageManager.DELETE_KEEP_DATA | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP); - deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(res.mRemovedInfo, + deletePackageAction = DeletePackageHelper.mayDeletePackageLocked( + installRequest.getRemovedInfo(), prepareResult.mOriginalPs, prepareResult.mDisabledPs, deleteFlags, null /* all users */); if (deletePackageAction == null) { @@ -146,8 +146,8 @@ final class ReconcilePackageUtils { request.mVersionInfos.get(installPackageName); final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo); final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo); - final boolean isRollback = installArgs != null - && installArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK; + final boolean isRollback = installRequest != null + && installRequest.isRollback(); final boolean compatMatch = PackageManagerServiceUtils.verifySignatures(signatureCheckPs, sharedUserSetting, disabledPkgSetting, @@ -257,8 +257,8 @@ final class ReconcilePackageUtils { } result.put(installPackageName, - new ReconciledPackage(request, installArgs, scanResult.mPkgSetting, - res, request.mPreparedPackages.get(installPackageName), scanResult, + new ReconciledPackage(request, installRequest, scanResult.mPkgSetting, + request.mPreparedPackages.get(installPackageName), scanResult, deletePackageAction, allowedSharedLibInfos, signingDetails, sharedUserSignaturesChanged, removeAppKeySetData)); } diff --git a/services/core/java/com/android/server/pm/ReconcileRequest.java b/services/core/java/com/android/server/pm/ReconcileRequest.java index 84b292ff4068..3568c153de91 100644 --- a/services/core/java/com/android/server/pm/ReconcileRequest.java +++ b/services/core/java/com/android/server/pm/ReconcileRequest.java @@ -34,20 +34,17 @@ final class ReconcileRequest { public final Map<String, ScanResult> mScannedPackages; public final Map<String, AndroidPackage> mAllPackages; - public final Map<String, InstallArgs> mInstallArgs; - public final Map<String, PackageInstalledInfo> mInstallResults; + public final Map<String, InstallRequest> mInstallRequests; public final Map<String, PrepareResult> mPreparedPackages; public final Map<String, Settings.VersionInfo> mVersionInfos; ReconcileRequest(Map<String, ScanResult> scannedPackages, - Map<String, InstallArgs> installArgs, - Map<String, PackageInstalledInfo> installResults, + Map<String, InstallRequest> installRequests, Map<String, PrepareResult> preparedPackages, Map<String, AndroidPackage> allPackages, Map<String, Settings.VersionInfo> versionInfos) { mScannedPackages = scannedPackages; - mInstallArgs = installArgs; - mInstallResults = installResults; + mInstallRequests = installRequests; mPreparedPackages = preparedPackages; mAllPackages = allPackages; mVersionInfos = versionInfos; @@ -56,7 +53,7 @@ final class ReconcileRequest { ReconcileRequest(Map<String, ScanResult> scannedPackages, Map<String, AndroidPackage> allPackages, Map<String, Settings.VersionInfo> versionInfos) { - this(scannedPackages, Collections.emptyMap(), Collections.emptyMap(), + this(scannedPackages, Collections.emptyMap(), Collections.emptyMap(), allPackages, versionInfos); } } diff --git a/services/core/java/com/android/server/pm/ReconciledPackage.java b/services/core/java/com/android/server/pm/ReconciledPackage.java index 3bb5a1b66338..d4da6c798b15 100644 --- a/services/core/java/com/android/server/pm/ReconciledPackage.java +++ b/services/core/java/com/android/server/pm/ReconciledPackage.java @@ -37,9 +37,8 @@ final class ReconciledPackage { public final PackageSetting mPkgSetting; public final ScanResult mScanResult; // TODO: Remove install-specific details from the reconcile result - public final PackageInstalledInfo mInstallResult; @Nullable public final PrepareResult mPrepareResult; - @Nullable public final InstallArgs mInstallArgs; + @Nullable public final InstallRequest mInstallRequest; public final DeletePackageAction mDeletePackageAction; public final List<SharedLibraryInfo> mAllowedSharedLibraryInfos; public final SigningDetails mSigningDetails; @@ -48,9 +47,8 @@ final class ReconciledPackage { public final boolean mRemoveAppKeySetData; ReconciledPackage(ReconcileRequest request, - InstallArgs installArgs, + InstallRequest installRequest, PackageSetting pkgSetting, - PackageInstalledInfo installResult, PrepareResult prepareResult, ScanResult scanResult, DeletePackageAction deletePackageAction, @@ -59,9 +57,8 @@ final class ReconciledPackage { boolean sharedUserSignaturesChanged, boolean removeAppKeySetData) { mRequest = request; - mInstallArgs = installArgs; + mInstallRequest = installRequest; mPkgSetting = pkgSetting; - mInstallResult = installResult; mPrepareResult = prepareResult; mScanResult = scanResult; mDeletePackageAction = deletePackageAction; diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index a02596538627..55d4b369632e 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -385,29 +385,29 @@ final class RemovePackageHelper { } } - void cleanUpResources(InstallArgs args) { + void cleanUpResources(File codeFile, String[] instructionSets) { synchronized (mPm.mInstallLock) { - cleanUpResourcesLI(args); + cleanUpResourcesLI(codeFile, instructionSets); } } // Need installer lock especially for dex file removal. @GuardedBy("mPm.mInstallLock") - private void cleanUpResourcesLI(InstallArgs args) { + private void cleanUpResourcesLI(File codeFile, String[] instructionSets) { // Try enumerating all code paths before deleting List<String> allCodePaths = Collections.EMPTY_LIST; - if (args.mCodeFile != null && args.mCodeFile.exists()) { + if (codeFile != null && codeFile.exists()) { final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( - input.reset(), args.mCodeFile, /* flags */ 0); + input.reset(), codeFile, /* flags */ 0); if (result.isSuccess()) { // Ignore error; we tried our best allCodePaths = result.getResult().getAllApkPaths(); } } - removeCodePathLI(args.mCodeFile); - removeDexFilesLI(allCodePaths, args.mInstructionSets); + removeCodePathLI(codeFile); + removeDexFilesLI(allCodePaths, instructionSets); } @GuardedBy("mPm.mInstallLock") diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 073776f14e68..37abeacf7a3b 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -781,7 +781,7 @@ final class DefaultPermissionGrantPolicy { for (String voiceInteractPackageName : voiceInteractPackageNames) { grantPermissionsToSystemPackage(pm, voiceInteractPackageName, userId, CONTACTS_PERMISSIONS, CALENDAR_PERMISSIONS, MICROPHONE_PERMISSIONS, - PHONE_PERMISSIONS, SMS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, + PHONE_PERMISSIONS, SMS_PERMISSIONS, COARSE_BACKGROUND_LOCATION_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS, NOTIFICATION_PERMISSIONS); } } diff --git a/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java b/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java index 1fb7cf5249cc..421f7ce9cf58 100644 --- a/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java +++ b/services/core/java/com/android/server/timedetector/GnssTimeUpdateService.java @@ -260,10 +260,11 @@ public final class GnssTimeUpdateService extends Binder { private void suggestGnssTime(LocationTime locationTime) { logDebug("suggestGnssTime()"); - long gnssTime = locationTime.getTime(); + long gnssUnixEpochTimeMillis = locationTime.getUnixEpochTimeMillis(); long elapsedRealtimeMs = locationTime.getElapsedRealtimeNanos() / 1_000_000L; - TimestampedValue<Long> timeSignal = new TimestampedValue<>(elapsedRealtimeMs, gnssTime); + TimestampedValue<Long> timeSignal = + new TimestampedValue<>(elapsedRealtimeMs, gnssUnixEpochTimeMillis); mLastSuggestedGnssTime = timeSignal; GnssTimeSuggestion timeSuggestion = new GnssTimeSuggestion(timeSignal); diff --git a/services/core/java/com/android/server/utils/AlarmQueue.java b/services/core/java/com/android/server/utils/AlarmQueue.java index 41373cd6bbe6..09ba19508476 100644 --- a/services/core/java/com/android/server/utils/AlarmQueue.java +++ b/services/core/java/com/android/server/utils/AlarmQueue.java @@ -64,8 +64,11 @@ public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener { * The pair is the key and its associated alarm time (in the elapsed realtime timebase). */ private static class AlarmPriorityQueue<Q> extends PriorityQueue<Pair<Q, Long>> { + private static final Comparator<Pair<?, Long>> sTimeComparator = + (o1, o2) -> Long.compare(o1.second, o2.second); + AlarmPriorityQueue() { - super(1, Comparator.comparingLong(o -> o.second)); + super(1, sTimeComparator); } /** diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 1c583be4af45..0637ed3d663b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2755,6 +2755,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } final StartingSurfaceController.StartingSurface surface; + final WindowState startingWindow = mStartingWindow; final boolean animate; if (mStartingData != null) { animate = prepareAnimation && mStartingData.needRevealAnimation() @@ -2779,7 +2780,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } - surface.remove(animate); + if (animate && mTransitionController.inCollectingTransition(startingWindow) + && startingWindow.cancelAndRedraw()) { + // Defer remove starting window after transition start. + // If splash screen window was in collecting, the client side is unable to draw because + // of Session#cancelDraw, which will blocking the remove animation. + startingWindow.mSyncTransaction.addTransactionCommittedListener(Runnable::run, () -> { + synchronized (mAtmService.mGlobalLock) { + surface.remove(true); + } + }); + } else { + surface.remove(animate); + } } /** @@ -8033,11 +8046,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Horizontal position int offsetX = 0; if (parentBounds.width() != screenResolvedBounds.width()) { - if (screenResolvedBounds.width() >= parentAppBounds.width()) { - // If resolved bounds overlap with insets, center within app bounds. - offsetX = getCenterOffset( - parentAppBounds.width(), screenResolvedBounds.width()); - } else { + if (screenResolvedBounds.width() <= parentAppBounds.width()) { float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier( newParentConfiguration); @@ -8049,11 +8058,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Vertical position int offsetY = 0; if (parentBounds.height() != screenResolvedBounds.height()) { - if (screenResolvedBounds.height() >= parentAppBounds.height()) { - // If resolved bounds overlap with insets, center within app bounds. - offsetY = getCenterOffset( - parentAppBounds.height(), screenResolvedBounds.height()); - } else { + if (screenResolvedBounds.height() <= parentAppBounds.height()) { float positionMultiplier = mLetterboxUiController.getVerticalPositionMultiplier( newParentConfiguration); @@ -8071,6 +8076,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A offsetBounds(resolvedConfig, offsetX, offsetY); } + // If the top is aligned with parentAppBounds add the vertical insets back so that the app + // content aligns with the status bar + if (resolvedConfig.windowConfiguration.getAppBounds().top == parentAppBounds.top) { + resolvedConfig.windowConfiguration.getBounds().top = parentBounds.top; + if (mSizeCompatBounds != null) { + mSizeCompatBounds.top = parentBounds.top; + } + } + // Since bounds has changed, the configuration needs to be computed accordingly. getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); } @@ -8448,7 +8462,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Above coordinates are in "@" space, now place "*" and "#" to screen space. final boolean fillContainer = resolvedBounds.equals(containingBounds); final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left; - final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top; + final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top; if (screenPosX != 0 || screenPosY != 0) { if (mSizeCompatBounds != null) { @@ -8794,24 +8808,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Also account for the insets (e.g. display cutouts, navigation bar), which will be // clipped away later in {@link Task#computeConfigResourceOverrides()}, i.e., the out // bounds are the app bounds restricted by aspect ratio + clippable insets. Otherwise, - // the app bounds would end up too small. + // the app bounds would end up too small. To achieve this we will also add clippable insets + // when the corresponding dimension fully fills the parent + int right = activityWidth + containingAppBounds.left; + int left = containingAppBounds.left; if (right >= containingAppBounds.right) { - right += containingBounds.right - containingAppBounds.right; + right = containingBounds.right; + left = containingBounds.left; } int bottom = activityHeight + containingAppBounds.top; + int top = containingAppBounds.top; if (bottom >= containingAppBounds.bottom) { - bottom += containingBounds.bottom - containingAppBounds.bottom; + bottom = containingBounds.bottom; + top = containingBounds.top; } - outBounds.set(containingBounds.left, containingBounds.top, right, bottom); - - // If the bounds are restricted by fixed aspect ratio, then out bounds should be put in the - // container app bounds. Otherwise the entire container bounds are available. - if (!outBounds.equals(containingBounds)) { - // The horizontal position should not cover insets (e.g. display cutout). - outBounds.left = containingAppBounds.left; - } - + outBounds.set(left, top, right, bottom); return true; } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 7e62c61572d6..2ea6a3f14f64 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -467,16 +467,6 @@ public class AppTransitionController { return TRANSIT_OLD_WALLPAPER_OPEN; } - // Some devices don't show a wallpaper. In that case we should still trigger wallpaper - // transitions when animating to/from the home activity - if (wallpaperTarget == null) { - if (topOpeningApp != null && topOpeningApp.isActivityTypeHome()) { - return TRANSIT_OLD_WALLPAPER_OPEN; - } else if (topClosingApp != null && topClosingApp.isActivityTypeHome()) { - return TRANSIT_OLD_WALLPAPER_CLOSE; - } - } - final ArraySet<WindowContainer> openingWcs = getAnimationTargets( openingApps, closingApps, true /* visible */); final ArraySet<WindowContainer> closingWcs = getAnimationTargets( @@ -488,6 +478,11 @@ public class AppTransitionController { @TransitContainerType int openingType = getTransitContainerType(openingContainer); @TransitContainerType int closingType = getTransitContainerType(closingContainer); if (appTransition.containsTransitRequest(TRANSIT_TO_FRONT) && openingType == TYPE_TASK) { + if (topOpeningApp != null && topOpeningApp.isActivityTypeHome()) { + // If we are opening the home task, we want to play an animation as if + // the task on top is being brought to back. + return TRANSIT_OLD_TASK_TO_BACK; + } return TRANSIT_OLD_TASK_TO_FRONT; } if (appTransition.containsTransitRequest(TRANSIT_TO_BACK) && closingType == TYPE_TASK) { diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java index 219092baface..1266db5bff98 100644 --- a/services/core/java/com/android/server/wm/AsyncRotationController.java +++ b/services/core/java/com/android/server/wm/AsyncRotationController.java @@ -357,7 +357,12 @@ class AsyncRotationController extends FadeAnimationController implements Consume * or seamless transformation in a rotated display. */ boolean shouldFreezeInsetsPosition(WindowState w) { - return mTransitionOp != OP_LEGACY && w.mTransitionController.inTransition() + if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) { + // Expect a screenshot layer has covered the screen, so it is fine to let client side + // insets animation runner update the position directly. + return false; + } + return mTransitionOp != OP_LEGACY && !mIsStartTransactionCommitted && isTargetToken(w.mToken); } diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index f3bd1a15cc94..2de8faf8b086 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -64,6 +64,7 @@ import android.view.InsetsState.InternalInsetsType; import android.view.InternalInsetsAnimationController; import android.view.SurfaceControl; import android.view.SyncRtSurfaceTransactionApplier; +import android.view.WindowInsets; import android.view.WindowInsets.Type; import android.view.WindowInsetsAnimation; import android.view.WindowInsetsAnimation.Bounds; @@ -398,16 +399,13 @@ class InsetsPolicy { if (WindowConfiguration.isFloating(windowingMode) || (windowingMode == WINDOWING_MODE_MULTI_WINDOW && isAlwaysOnTop)) { - InsetsState newState = new InsetsState(); - - // Only caption and IME are needed. - if (state.peekSource(ITYPE_CAPTION_BAR) != null) { - newState.addSource(state.peekSource(ITYPE_CAPTION_BAR)); - } - if (windowingMode != WINDOWING_MODE_PINNED && state.peekSource(ITYPE_IME) != null) { - newState.addSource(state.peekSource(ITYPE_IME)); + // Keep frames, caption, and IME. + int types = WindowInsets.Type.captionBar(); + if (windowingMode != WINDOWING_MODE_PINNED) { + types |= WindowInsets.Type.ime(); } - + InsetsState newState = new InsetsState(); + newState.set(state, types); state = newState; stateCopied = true; } diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java index 27550d9e8186..3d006868c79c 100644 --- a/services/core/java/com/android/server/wm/Letterbox.java +++ b/services/core/java/com/android/server/wm/Letterbox.java @@ -273,8 +273,8 @@ public class Letterbox { @Override public boolean onDoubleTapEvent(MotionEvent e) { if (e.getAction() == MotionEvent.ACTION_UP) { - mDoubleTapCallbackX.accept((int) e.getX()); - mDoubleTapCallbackY.accept((int) e.getY()); + mDoubleTapCallbackX.accept((int) e.getRawX()); + mDoubleTapCallbackY.accept((int) e.getRawY()); return true; } return false; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 88c47dbaaab4..9722bb61f2c6 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -9277,4 +9277,38 @@ public class WindowManagerService extends IWindowManager.Stub "Unexpected letterbox background type: " + letterboxBackgroundType); } } + + @Override + public void captureDisplay(int displayId, @Nullable ScreenCapture.CaptureArgs captureArgs, + ScreenCapture.ScreenCaptureListener listener) { + Slog.d(TAG, "captureDisplay"); + if (!checkCallingPermission(READ_FRAME_BUFFER, "captureDisplay()")) { + throw new SecurityException("Requires READ_FRAME_BUFFER permission"); + } + + final SurfaceControl displaySurfaceControl; + synchronized (mGlobalLock) { + DisplayContent displayContent = mRoot.getDisplayContent(displayId); + if (displayContent == null) { + throw new IllegalArgumentException("Trying to screenshot and invalid display: " + + displayId); + } + + displaySurfaceControl = displayContent.getSurfaceControl(); + + if (captureArgs == null) { + displayContent.getBounds(mTmpRect); + mTmpRect.offsetTo(0, 0); + captureArgs = new ScreenCapture.CaptureArgs.Builder<>() + .setSourceCrop(mTmpRect) + .build(); + } + } + + ScreenCapture.LayerCaptureArgs args = + new ScreenCapture.LayerCaptureArgs.Builder(displaySurfaceControl, captureArgs) + .build(); + + ScreenCapture.captureLayers(args, listener); + } } diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp index daca1531d41f..b7a4fd1be261 100644 --- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp +++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp @@ -37,6 +37,7 @@ enum class DeviceType { KEYBOARD, MOUSE, TOUCHSCREEN, + DPAD, }; enum class UinputAction { @@ -76,6 +77,13 @@ static std::map<int, int> TOOL_TYPE_MAPPING = { {AMOTION_EVENT_TOOL_TYPE_PALM, MT_TOOL_PALM}, }; +// Dpad keycode mapping from https://source.android.com/devices/input/keyboard-devices +static std::map<int, int> DPAD_KEY_CODE_MAPPING = { + {AKEYCODE_DPAD_DOWN, KEY_DOWN}, {AKEYCODE_DPAD_UP, KEY_UP}, + {AKEYCODE_DPAD_LEFT, KEY_LEFT}, {AKEYCODE_DPAD_RIGHT, KEY_RIGHT}, + {AKEYCODE_DPAD_CENTER, KEY_SELECT}, +}; + // Keycode mapping from https://source.android.com/devices/input/keyboard-devices static std::map<int, int> KEY_CODE_MAPPING = { {AKEYCODE_0, KEY_0}, @@ -200,8 +208,13 @@ static int openUinput(const char* readableName, jint vendorId, jint productId, c ioctl(fd, UI_SET_EVBIT, EV_KEY); ioctl(fd, UI_SET_EVBIT, EV_SYN); switch (deviceType) { + case DeviceType::DPAD: + for (const auto& [_, keyCode] : DPAD_KEY_CODE_MAPPING) { + ioctl(fd, UI_SET_KEYBIT, keyCode); + } + break; case DeviceType::KEYBOARD: - for (const auto& [ignored, keyCode] : KEY_CODE_MAPPING) { + for (const auto& [_, keyCode] : KEY_CODE_MAPPING) { ioctl(fd, UI_SET_KEYBIT, keyCode); } break; @@ -327,6 +340,12 @@ static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productI screenHeight, screenWidth); } +static int nativeOpenUinputDpad(JNIEnv* env, jobject thiz, jstring name, jint vendorId, + jint productId, jstring phys) { + return openUinputJni(env, name, vendorId, productId, phys, DeviceType::DPAD, + /* screenHeight */ 0, /* screenWidth */ 0); +} + static int nativeOpenUinputKeyboard(JNIEnv* env, jobject thiz, jstring name, jint vendorId, jint productId, jstring phys) { return openUinputJni(env, name, vendorId, productId, phys, DeviceType::KEYBOARD, @@ -355,10 +374,10 @@ static bool writeInputEvent(int fd, uint16_t type, uint16_t code, int32_t value) return TEMP_FAILURE_RETRY(write(fd, &ev, sizeof(struct input_event))) == sizeof(ev); } -static bool nativeWriteKeyEvent(JNIEnv* env, jobject thiz, jint fd, jint androidKeyCode, - jint action) { - auto keyCodeIterator = KEY_CODE_MAPPING.find(androidKeyCode); - if (keyCodeIterator == KEY_CODE_MAPPING.end()) { +static bool writeKeyEvent(jint fd, jint androidKeyCode, jint action, + const std::map<int, int>& keyCodeMapping) { + auto keyCodeIterator = keyCodeMapping.find(androidKeyCode); + if (keyCodeIterator == keyCodeMapping.end()) { ALOGE("No supportive native keycode for androidKeyCode %d", androidKeyCode); return false; } @@ -376,6 +395,16 @@ static bool nativeWriteKeyEvent(JNIEnv* env, jobject thiz, jint fd, jint android return true; } +static bool nativeWriteDpadKeyEvent(JNIEnv* env, jobject thiz, jint fd, jint androidKeyCode, + jint action) { + return writeKeyEvent(fd, androidKeyCode, action, DPAD_KEY_CODE_MAPPING); +} + +static bool nativeWriteKeyEvent(JNIEnv* env, jobject thiz, jint fd, jint androidKeyCode, + jint action) { + return writeKeyEvent(fd, androidKeyCode, action, KEY_CODE_MAPPING); +} + static bool nativeWriteButtonEvent(JNIEnv* env, jobject thiz, jint fd, jint buttonCode, jint action) { auto buttonCodeIterator = BUTTON_CODE_MAPPING.find(buttonCode); @@ -461,6 +490,8 @@ static bool nativeWriteScrollEvent(JNIEnv* env, jobject thiz, jint fd, jfloat xA } static JNINativeMethod methods[] = { + {"nativeOpenUinputDpad", "(Ljava/lang/String;IILjava/lang/String;)I", + (void*)nativeOpenUinputDpad}, {"nativeOpenUinputKeyboard", "(Ljava/lang/String;IILjava/lang/String;)I", (void*)nativeOpenUinputKeyboard}, {"nativeOpenUinputMouse", "(Ljava/lang/String;IILjava/lang/String;)I", @@ -468,6 +499,7 @@ static JNINativeMethod methods[] = { {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)I", (void*)nativeOpenUinputTouchscreen}, {"nativeCloseUinput", "(I)Z", (void*)nativeCloseUinput}, + {"nativeWriteDpadKeyEvent", "(III)Z", (void*)nativeWriteDpadKeyEvent}, {"nativeWriteKeyEvent", "(III)Z", (void*)nativeWriteKeyEvent}, {"nativeWriteButtonEvent", "(III)Z", (void*)nativeWriteButtonEvent}, {"nativeWriteTouchEvent", "(IIIIFFFF)Z", (void*)nativeWriteTouchEvent}, diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java index cc5ed92e02d9..51bd5b0f76c9 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java @@ -57,6 +57,8 @@ class InputManagerMockHelper { doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputMouse( anyString(), anyInt(), anyInt(), anyString()); + doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputDpad( + anyString(), anyInt(), anyInt(), anyString()); doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputKeyboard( anyString(), anyInt(), anyInt(), anyString()); doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputTouchscreen( diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index cc2cdba69af6..ef203d03fd98 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -387,6 +387,14 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void createVirtualDpad_noDisplay_failsSecurityException() { + assertThrows( + SecurityException.class, + () -> mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, + PRODUCT_ID, BINDER)); + } + + @Test public void createVirtualKeyboard_noDisplay_failsSecurityException() { assertThrows( SecurityException.class, @@ -418,6 +426,17 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void createVirtualDpad_noPermission_failsSecurityException() { + mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); + doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); + assertThrows( + SecurityException.class, + () -> mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, + PRODUCT_ID, BINDER)); + } + + @Test public void createVirtualKeyboard_noPermission_failsSecurityException() { mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( @@ -468,6 +487,17 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void createVirtualDpad_hasDisplay_obtainFileDescriptor() { + mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); + mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, + BINDER); + assertWithMessage("Virtual dpad should register fd when the display matches") + .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); + verify(mNativeWrapperMock).openUinputDpad(eq(DEVICE_NAME), eq(VENDOR_ID), + eq(PRODUCT_ID), anyString()); + } + + @Test public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() { mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, @@ -483,7 +513,7 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER); - assertWithMessage("Virtual keyboard should register fd when the display matches") + assertWithMessage("Virtual mouse should register fd when the display matches") .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); verify(mNativeWrapperMock).openUinputMouse(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID), anyString()); @@ -494,7 +524,7 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)); - assertWithMessage("Virtual keyboard should register fd when the display matches") + assertWithMessage("Virtual touchscreen should register fd when the display matches") .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH)); diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt index 6e422fa7af7f..d118661b84f5 100644 --- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt +++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt @@ -20,16 +20,20 @@ import android.content.Context import android.content.ContextWrapper import android.hardware.BatteryState.STATUS_CHARGING import android.hardware.BatteryState.STATUS_FULL +import android.hardware.BatteryState.STATUS_UNKNOWN import android.hardware.input.IInputDeviceBatteryListener +import android.hardware.input.IInputDevicesChangedListener import android.hardware.input.IInputManager -import android.hardware.input.InputDeviceCountryCode import android.hardware.input.InputManager import android.os.Binder import android.os.IBinder +import android.os.test.TestLooper import android.platform.test.annotations.Presubmit import android.view.InputDevice import androidx.test.InstrumentationRegistry +import com.android.server.input.BatteryController.UEventManager import org.junit.After +import org.junit.Assert.assertEquals import org.junit.Assert.fail import org.junit.Before import org.junit.Rule @@ -39,15 +43,27 @@ import org.mockito.ArgumentMatchers.notNull import org.mockito.Mock import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyLong +import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.eq import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit +private fun createInputDevice(deviceId: Int, hasBattery: Boolean = true): InputDevice = + InputDevice.Builder() + .setId(deviceId) + .setName("Device $deviceId") + .setDescriptor("descriptor $deviceId") + .setExternal(true) + .setHasBattery(hasBattery) + .setGeneration(0) + .build() + /** * Tests for {@link InputDeviceBatteryController}. * @@ -60,6 +76,7 @@ class BatteryControllerTests { const val PID = 42 const val DEVICE_ID = 13 const val SECOND_DEVICE_ID = 11 + const val TIMESTAMP = 123456789L } @get:Rule @@ -69,13 +86,19 @@ class BatteryControllerTests { private lateinit var native: NativeInputManagerService @Mock private lateinit var iInputManager: IInputManager + @Mock + private lateinit var uEventManager: UEventManager private lateinit var batteryController: BatteryController private lateinit var context: Context + private lateinit var testLooper: TestLooper + private lateinit var devicesChangedListener: IInputDevicesChangedListener + private val deviceGenerationMap = mutableMapOf<Int /*deviceId*/, Int /*generation*/>() @Before fun setup() { context = spy(ContextWrapper(InstrumentationRegistry.getContext())) + testLooper = TestLooper() val inputManager = InputManager.resetInstance(iInputManager) `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager) `when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID, SECOND_DEVICE_ID)) @@ -83,17 +106,18 @@ class BatteryControllerTests { `when`(iInputManager.getInputDevice(SECOND_DEVICE_ID)) .thenReturn(createInputDevice(SECOND_DEVICE_ID)) - batteryController = BatteryController(context, native) + batteryController = BatteryController(context, native, testLooper.looper, uEventManager) + batteryController.systemRunning() + val listenerCaptor = ArgumentCaptor.forClass(IInputDevicesChangedListener::class.java) + verify(iInputManager).registerInputDevicesChangedListener(listenerCaptor.capture()) + devicesChangedListener = listenerCaptor.value } - private fun createInputDevice(deviceId: Int): InputDevice = - InputDevice.Builder() - .setId(deviceId) - .setName("Device $deviceId") - .setDescriptor("descriptor $deviceId") - .setExternal(true) - .setHasBattery(true) - .build() + private fun notifyDeviceChanged(deviceId: Int) { + deviceGenerationMap[deviceId] = deviceGenerationMap[deviceId]?.plus(1) ?: 1 + val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) } + devicesChangedListener.onInputDevicesChanged(list.toIntArray()) + } @After fun tearDown() { @@ -169,4 +193,68 @@ class BatteryControllerTests { verify(listener).onBatteryStateChanged(eq(SECOND_DEVICE_ID), eq(true /*isPresent*/), eq(STATUS_CHARGING), eq(0.78f), anyLong()) } + + @Test + fun testListenersNotifiedOnUEventNotification() { + `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/test/device1") + `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING) + `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78) + val listener = createMockListener() + val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java) + batteryController.registerBatteryListener(DEVICE_ID, listener, PID) + verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1")) + verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), + eq(STATUS_CHARGING), eq(0.78f), anyLong()) + + // If the battery state has changed when an UEvent is sent, the listeners are notified. + `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80) + uEventListener.value!!.onUEvent(TIMESTAMP) + verify(listener).onBatteryStateChanged(DEVICE_ID, true /*isPresent*/, STATUS_CHARGING, + 0.80f, TIMESTAMP) + + // If the battery state has not changed when an UEvent is sent, the listeners are not + // notified. + clearInvocations(listener) + uEventListener.value!!.onUEvent(TIMESTAMP + 1) + verifyNoMoreInteractions(listener) + + batteryController.unregisterBatteryListener(DEVICE_ID, listener, PID) + verify(uEventManager).removeListener(uEventListener.capture()) + assertEquals("The same observer must be registered and unregistered", + uEventListener.allValues[0], uEventListener.allValues[1]) + } + + @Test + fun testBatteryPresenceChanged() { + `when`(native.getBatteryDevicePath(DEVICE_ID)).thenReturn("/test/device1") + `when`(native.getBatteryStatus(DEVICE_ID)).thenReturn(STATUS_CHARGING) + `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(78) + val listener = createMockListener() + val uEventListener = ArgumentCaptor.forClass(UEventManager.UEventListener::class.java) + batteryController.registerBatteryListener(DEVICE_ID, listener, PID) + verify(uEventManager).addListener(uEventListener.capture(), eq("DEVPATH=/test/device1")) + verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), + eq(STATUS_CHARGING), eq(0.78f), anyLong()) + + // If the battery presence for the InputDevice changes, the listener is notified. + `when`(iInputManager.getInputDevice(DEVICE_ID)) + .thenReturn(createInputDevice(DEVICE_ID, hasBattery = false)) + notifyDeviceChanged(DEVICE_ID) + testLooper.dispatchNext() + verify(listener).onBatteryStateChanged(eq(DEVICE_ID), eq(false /*isPresent*/), + eq(STATUS_UNKNOWN), eq(Float.NaN), anyLong()) + // Since the battery is no longer present, the UEventListener should be removed. + verify(uEventManager).removeListener(uEventListener.value) + + // If the battery becomes present again, the listener is notified. + `when`(iInputManager.getInputDevice(DEVICE_ID)) + .thenReturn(createInputDevice(DEVICE_ID, hasBattery = true)) + notifyDeviceChanged(DEVICE_ID) + testLooper.dispatchNext() + verify(listener, times(2)).onBatteryStateChanged(eq(DEVICE_ID), eq(true /*isPresent*/), + eq(STATUS_CHARGING), eq(0.78f), anyLong()) + // Ensure that a new UEventListener was added. + verify(uEventManager, times(2)) + .addListener(uEventListener.capture(), eq("DEVPATH=/test/device1")) + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 49879efe4b51..798604306b43 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -1704,8 +1704,8 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test - public void testInfoIsPermittedForProfile_notAllowed() { - when(mUserProfiles.canProfileUseBoundServices(anyInt())).thenReturn(false); + public void testInfoIsPermittedForProfile_notProfile() { + when(mUserProfiles.isProfileUser(anyInt())).thenReturn(false); IInterface service = mock(IInterface.class); when(service.asBinder()).thenReturn(mock(IBinder.class)); @@ -1714,12 +1714,12 @@ public class ManagedServicesTest extends UiServiceTestCase { services.registerSystemService(service, null, 10, 1000); ManagedServices.ManagedServiceInfo info = services.checkServiceTokenLocked(service); - assertFalse(info.isPermittedForProfile(0)); + assertTrue(info.isPermittedForProfile(0)); } @Test - public void testInfoIsPermittedForProfile_allows() { - when(mUserProfiles.canProfileUseBoundServices(anyInt())).thenReturn(true); + public void testInfoIsPermittedForProfile_profileAndDpmAllows() { + when(mUserProfiles.isProfileUser(anyInt())).thenReturn(true); when(mDpm.isNotificationListenerServicePermitted(anyString(), anyInt())).thenReturn(true); IInterface service = mock(IInterface.class); @@ -1734,6 +1734,22 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + public void testInfoIsPermittedForProfile_profileAndDpmDenies() { + when(mUserProfiles.isProfileUser(anyInt())).thenReturn(true); + when(mDpm.isNotificationListenerServicePermitted(anyString(), anyInt())).thenReturn(false); + + IInterface service = mock(IInterface.class); + when(service.asBinder()).thenReturn(mock(IBinder.class)); + ManagedServices services = new TestManagedServices(getContext(), mLock, mUserProfiles, + mIpm, APPROVAL_BY_PACKAGE); + services.registerSystemService(service, null, 10, 1000); + ManagedServices.ManagedServiceInfo info = services.checkServiceTokenLocked(service); + info.component = new ComponentName("a","b"); + + assertFalse(info.isPermittedForProfile(0)); + } + + @Test public void testUserProfiles_canProfileUseBoundServices_managedProfile() { List<UserInfo> users = new ArrayList<>(); UserInfo profile = new UserInfo(ActivityManager.getCurrentUser(), "current", 0); @@ -1750,9 +1766,9 @@ public class ManagedServicesTest extends UiServiceTestCase { ManagedServices.UserProfiles profiles = new ManagedServices.UserProfiles(); profiles.updateCache(mContext); - assertTrue(profiles.canProfileUseBoundServices(ActivityManager.getCurrentUser())); - assertFalse(profiles.canProfileUseBoundServices(12)); - assertFalse(profiles.canProfileUseBoundServices(13)); + assertFalse(profiles.isProfileUser(ActivityManager.getCurrentUser())); + assertTrue(profiles.isProfileUser(12)); + assertTrue(profiles.isProfileUser(13)); } private void resetComponentsAndPackages() { diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 2918365b94c8..107bbe1c79e3 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -79,7 +79,10 @@ <activity android:name="com.android.server.wm.ActivityOptionsTest$MainActivity" android:turnScreenOn="true" android:showWhenLocked="true" /> - <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity" /> + <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity" + android:theme="@style/WhiteBackgroundTheme" + android:turnScreenOn="true" + android:showWhenLocked="true"/> <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/> <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService" diff --git a/services/tests/wmtests/res/values/styles.xml b/services/tests/wmtests/res/values/styles.xml new file mode 100644 index 000000000000..6857ff99e9b8 --- /dev/null +++ b/services/tests/wmtests/res/values/styles.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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> + <style name="WhiteBackgroundTheme" parent="@android:style/Theme.DeviceDefault"> + <item name="android:windowNoTitle">true</item> + <item name="android:windowFullscreen">true</item> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> + </style> +</resources> diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java index 0b58428e9bfb..8cd8e9bde1b7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java @@ -16,6 +16,10 @@ package com.android.server.wm; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowInsets.Type.displayCutout; +import static android.view.WindowInsets.Type.statusBars; + import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertNotNull; @@ -23,20 +27,31 @@ import static org.junit.Assert.assertTrue; import android.app.Activity; import android.app.Instrumentation; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.ColorSpace; import android.graphics.GraphicBuffer; +import android.graphics.Insets; import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.DataSpace; +import android.hardware.HardwareBuffer; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; import android.platform.test.annotations.Presubmit; +import android.view.IWindowManager; import android.view.PointerIcon; import android.view.SurfaceControl; -import android.view.WindowManager; +import android.view.cts.surfacevalidator.BitmapPixelChecker; +import android.view.cts.surfacevalidator.PixelColor; +import android.view.cts.surfacevalidator.SaveBitmapHelper; import android.window.ScreenCapture; +import android.window.ScreenCapture.SyncScreenCaptureListener; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; @@ -45,6 +60,7 @@ import androidx.test.rule.ActivityTestRule; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestName; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -60,6 +76,8 @@ public class ScreenshotTests { private static final int BUFFER_HEIGHT = 100; private final Instrumentation mInstrumentation = getInstrumentation(); + @Rule + public TestName mTestName = new TestName(); @Rule public ActivityTestRule<ScreenshotActivity> mActivityRule = @@ -95,8 +113,8 @@ public class ScreenshotTests { buffer.unlockCanvasAndPost(canvas); t.show(secureSC) - .setBuffer(secureSC, buffer) - .setColorSpace(secureSC, ColorSpace.get(ColorSpace.Named.SRGB)) + .setBuffer(secureSC, HardwareBuffer.createFromGraphicBuffer(buffer)) + .setDataSpace(secureSC, DataSpace.DATASPACE_SRGB) .apply(true); ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC) @@ -112,15 +130,69 @@ public class ScreenshotTests { Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false); screenshot.recycle(); - int numMatchingPixels = PixelChecker.getNumMatchingPixels(swBitmap, - new PixelColor(PixelColor.RED)); - long sizeOfBitmap = swBitmap.getWidth() * swBitmap.getHeight(); + BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED); + Rect bounds = new Rect(0, 0, swBitmap.getWidth(), swBitmap.getHeight()); + int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds); + int sizeOfBitmap = bounds.width() * bounds.height(); boolean success = numMatchingPixels == sizeOfBitmap; swBitmap.recycle(); assertTrue(success); } + @Test + public void testCaptureDisplay() throws RemoteException { + IWindowManager windowManager = IWindowManager.Stub.asInterface( + ServiceManager.getService(Context.WINDOW_SERVICE)); + SurfaceControl sc = new SurfaceControl.Builder() + .setName("Layer") + .setCallsite("testCaptureDisplay") + .build(); + + SurfaceControl.Transaction t = mActivity.addChildSc(sc); + mInstrumentation.waitForIdleSync(); + + GraphicBuffer buffer = GraphicBuffer.create(BUFFER_WIDTH, BUFFER_HEIGHT, + PixelFormat.RGBA_8888, + GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER + | GraphicBuffer.USAGE_SW_WRITE_RARELY); + + Canvas canvas = buffer.lockCanvas(); + canvas.drawColor(Color.RED); + buffer.unlockCanvasAndPost(canvas); + + Point point = mActivity.getPositionBelowStatusBar(); + t.show(sc) + .setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer)) + .setDataSpace(sc, DataSpace.DATASPACE_SRGB) + .setPosition(sc, point.x, point.y) + .apply(true); + + SyncScreenCaptureListener listener = new SyncScreenCaptureListener(); + windowManager.captureDisplay(DEFAULT_DISPLAY, null, listener.getScreenCaptureListener()); + ScreenCapture.ScreenshotHardwareBuffer hardwareBuffer = listener.waitForScreenshot(); + assertNotNull(hardwareBuffer); + + Bitmap screenshot = hardwareBuffer.asBitmap(); + assertNotNull(screenshot); + + Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false); + screenshot.recycle(); + + BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED); + Rect bounds = new Rect(point.x, point.y, BUFFER_WIDTH + point.x, BUFFER_HEIGHT + point.y); + int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds); + int pixelMatchSize = bounds.width() * bounds.height(); + boolean success = numMatchingPixels == pixelMatchSize; + + if (!success) { + SaveBitmapHelper.saveBitmap(swBitmap, getClass(), mTestName, "failedImage"); + } + swBitmap.recycle(); + assertTrue("numMatchingPixels=" + numMatchingPixels + " pixelMatchSize=" + pixelMatchSize, + success); + } + public static class ScreenshotActivity extends Activity { private static final long WAIT_TIMEOUT_S = 5; private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -130,7 +202,6 @@ public class ScreenshotTests { super.onCreate(savedInstanceState); getWindow().getDecorView().setPointerIcon( PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL)); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl) { @@ -148,88 +219,14 @@ public class ScreenshotTests { } return t; } - } - public abstract static class PixelChecker { - static int getNumMatchingPixels(Bitmap bitmap, PixelColor pixelColor) { - int numMatchingPixels = 0; - for (int x = 0; x < bitmap.getWidth(); x++) { - for (int y = 0; y < bitmap.getHeight(); y++) { - int color = bitmap.getPixel(x, y); - if (matchesColor(pixelColor, color)) { - numMatchingPixels++; - } - } - } - return numMatchingPixels; - } - - static boolean matchesColor(PixelColor expectedColor, int color) { - final float red = Color.red(color); - final float green = Color.green(color); - final float blue = Color.blue(color); - final float alpha = Color.alpha(color); - - return alpha <= expectedColor.mMaxAlpha - && alpha >= expectedColor.mMinAlpha - && red <= expectedColor.mMaxRed - && red >= expectedColor.mMinRed - && green <= expectedColor.mMaxGreen - && green >= expectedColor.mMinGreen - && blue <= expectedColor.mMaxBlue - && blue >= expectedColor.mMinBlue; - } - } - - public static class PixelColor { - public static final int BLACK = 0xFF000000; - public static final int RED = 0xFF0000FF; - public static final int GREEN = 0xFF00FF00; - public static final int BLUE = 0xFFFF0000; - public static final int YELLOW = 0xFF00FFFF; - public static final int MAGENTA = 0xFFFF00FF; - public static final int WHITE = 0xFFFFFFFF; - - public static final int TRANSPARENT_RED = 0x7F0000FF; - public static final int TRANSPARENT_BLUE = 0x7FFF0000; - public static final int TRANSPARENT = 0x00000000; - - // Default to black - public short mMinAlpha; - public short mMaxAlpha; - public short mMinRed; - public short mMaxRed; - public short mMinBlue; - public short mMaxBlue; - public short mMinGreen; - public short mMaxGreen; - - public PixelColor(int color) { - short alpha = (short) ((color >> 24) & 0xFF); - short blue = (short) ((color >> 16) & 0xFF); - short green = (short) ((color >> 8) & 0xFF); - short red = (short) (color & 0xFF); - - mMinAlpha = (short) getMinValue(alpha); - mMaxAlpha = (short) getMaxValue(alpha); - mMinRed = (short) getMinValue(red); - mMaxRed = (short) getMaxValue(red); - mMinBlue = (short) getMinValue(blue); - mMaxBlue = (short) getMaxValue(blue); - mMinGreen = (short) getMinValue(green); - mMaxGreen = (short) getMaxValue(green); - } - - public PixelColor() { - this(BLACK); - } - - private int getMinValue(short color) { - return Math.max(color - 4, 0); - } + public Point getPositionBelowStatusBar() { + Insets statusBarInsets = getWindow() + .getDecorView() + .getRootWindowInsets() + .getInsets(statusBars() | displayCutout()); - private int getMaxValue(short color) { - return Math.min(color + 4, 0xFF); + return new Point(statusBarInsets.left, statusBarInsets.top); } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 9f7f3411b2b3..54b33e9794b6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -210,10 +210,8 @@ public class SizeCompatTests extends WindowTestsBase { assertFitted(); // After the orientation of activity is changed, the display is rotated, the aspect - // ratio should be the same (bounds=[100, 0 - 800, 583], appBounds=[100, 0 - 800, 583]). + // ratio should be the same (bounds=[0, 0 - 800, 583], appBounds=[100, 0 - 800, 583]). assertEquals(appBounds.width(), appBounds.height() * aspectRatio, 0.5f /* delta */); - // The notch is no longer on top. - assertEquals(appBounds, mActivity.getBounds()); // Activity max bounds are sandboxed. assertActivityMaxBoundsSandboxed(); @@ -467,8 +465,6 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(ROTATION_270, mTask.getWindowConfiguration().getRotation()); assertEquals(origBounds.width(), currentBounds.width()); - // The notch is on horizontal side, so current height changes from 1460 to 1400. - assertEquals(origBounds.height() - notchHeight, currentBounds.height()); // Make sure the app size is the same assertEquals(origAppBounds.width(), appBounds.width()); assertEquals(origAppBounds.height(), appBounds.height()); @@ -2271,7 +2267,7 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED); // Bounds are letterboxed to respect the provided max aspect ratio. - assertEquals(mActivity.getBounds(), new Rect(0, 850, 1000, 1950)); + assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 1100)); // Move activity to split screen which has landscape size. mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents */ false, "test"); @@ -2338,6 +2334,8 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testLetterboxDetailsForStatusBar_letterboxNotOverlappingStatusBar() { + // Align to center so that we don't overlap with the status bar + mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800) .setNotch(100) .build(); @@ -2354,7 +2352,7 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mRootWindowContainer.performSurfacePlacement(); Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds()); - assertEquals(mBounds, new Rect(0, 750, 1000, 1950)); + assertEquals(mBounds, new Rect(0, 900, 1000, 2000)); DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy(); LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails( @@ -2454,7 +2452,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(0, 0, 700, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(0, 700, 700, 2100), + /* sizeCompatUnscaled */ new Rect(0, 0, 700, 1400), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(0, 0, 350, 700)); } @@ -2467,7 +2465,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100), + /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); } @@ -2482,7 +2480,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100), + /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); @@ -2492,7 +2490,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100), + /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); } @@ -2505,7 +2503,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(2100, 0, 2800, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(700, 700, 1400, 2100), + /* sizeCompatUnscaled */ new Rect(700, 0, 1400, 1400), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(1050, 0, 1400, 700)); } @@ -2534,6 +2532,64 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testApplyAspectRatio_activityAlignWithParentAppVertical() { + // The display's app bounds will be (0, 100, 1000, 2350) + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500) + .setCanRotate(false) + .setCutout(0, 100, 0, 150) + .build(); + + setUpApp(display); + prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + // The activity height is 2100 and the display's app bounds height is 2250, so the activity + // can be aligned inside parentAppBounds + assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 2200)); + } + @Test + public void testApplyAspectRatio_activityCannotAlignWithParentAppVertical() { + // The display's app bounds will be (0, 100, 1000, 2150) + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2300) + .setCanRotate(false) + .setCutout(0, 100, 0, 150) + .build(); + + setUpApp(display); + prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + // The activity height is 2100 and the display's app bounds height is 2050, so the activity + // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display + assertEquals(mActivity.getBounds(), display.getBounds()); + } + + @Test + public void testApplyAspectRatio_activityAlignWithParentAppHorizontal() { + // The display's app bounds will be (100, 0, 2350, 1000) + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2500, 1000) + .setCanRotate(false) + .setCutout(100, 0, 150, 0) + .build(); + + setUpApp(display); + prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + // The activity width is 2100 and the display's app bounds width is 2250, so the activity + // can be aligned inside parentAppBounds + assertEquals(mActivity.getBounds(), new Rect(175, 0, 2275, 1000)); + } + @Test + public void testApplyAspectRatio_activityCannotAlignWithParentAppHorizontal() { + // The display's app bounds will be (100, 0, 2150, 1000) + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2300, 1000) + .setCanRotate(false) + .setCutout(100, 0, 150, 0) + .build(); + + setUpApp(display); + prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + // The activity width is 2100 and the display's app bounds width is 2050, so the activity + // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display + assertEquals(mActivity.getBounds(), display.getBounds()); + } + + @Test public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() { // When activity width equals parent width, multiplier shouldn't have any effect. assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( @@ -2608,6 +2664,25 @@ public class SizeCompatTests extends WindowTestsBase { /* sizeCompatScaled */ new Rect(0, 1050, 700, 1400)); } + @Test + public void testUpdateResolvedBoundsPosition_alignToTop() { + final int notchHeight = 100; + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800) + .setNotch(notchHeight) + .build(); + setUpApp(display); + + // Prepare unresizable activity with max aspect ratio + prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED); + + Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds()); + Rect appBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds()); + // The insets should be cut for aspect ratio and then added back because the appBounds + // are aligned to the top of the parentAppBounds + assertEquals(mBounds, new Rect(0, 0, 1000, 1200)); + assertEquals(appBounds, new Rect(0, notchHeight, 1000, 1200)); + } + private void assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity( float letterboxVerticalPositionMultiplier, Rect fixedOrientationLetterbox, Rect sizeCompatUnscaled, Rect sizeCompatScaled) { @@ -2955,7 +3030,7 @@ public class SizeCompatTests extends WindowTestsBase { rotateDisplay(mActivity.mDisplayContent, ROTATION_90); assertTrue(mActivity.inSizeCompatMode()); // Activity is in size compat mode but not scaled. - assertEquals(new Rect(0, 1050, 1400, 1750), mActivity.getBounds()); + assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds()); } private void assertVerticalPositionForDifferentDisplayConfigsForPortraitActivity( diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index aa3ca18073c3..bf1d1fa98249 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -143,11 +143,24 @@ class TestDisplayContent extends DisplayContent { mInfo.ownerUid = ownerUid; return this; } - Builder setNotch(int height) { + Builder setCutout(int left, int top, int right, int bottom) { + final int cutoutFillerSize = 80; + Rect boundLeft = left != 0 ? new Rect(0, 0, left, cutoutFillerSize) : null; + Rect boundTop = top != 0 ? new Rect(0, 0, cutoutFillerSize, top) : null; + Rect boundRight = right != 0 ? new Rect(mInfo.logicalWidth - right, 0, + mInfo.logicalWidth, cutoutFillerSize) : null; + Rect boundBottom = bottom != 0 + ? new Rect(0, mInfo.logicalHeight - bottom, cutoutFillerSize, + mInfo.logicalHeight) : null; + mInfo.displayCutout = new DisplayCutout( - Insets.of(0, height, 0, 0), null, new Rect(20, 0, 80, height), null, null); + Insets.of(left, top, right, bottom), + boundLeft, boundTop, boundRight, boundBottom); return this; } + Builder setNotch(int height) { + return setCutout(0, height, 0, 0); + } Builder setStatusBarHeight(int height) { mStatusBarHeight = height; return this; diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 64e15f26e62a..e2dff965ef96 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -732,7 +732,7 @@ public class TransitionTests extends WindowTestsBase { assertTrue(ime.mToken.inTransition()); assertTrue(task.inTransition()); assertTrue(asyncRotationController.isTargetToken(decorToken)); - assertTrue(asyncRotationController.shouldFreezeInsetsPosition(navBar)); + assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true); screenDecor.setOrientationChanging(false); // Status bar finishes drawing before the start transaction. Its fade-in animation will be @@ -747,6 +747,7 @@ public class TransitionTests extends WindowTestsBase { // The transaction is committed, so fade-in animation for status bar is consumed. transactionCommittedListener.onTransactionCommitted(); assertFalse(asyncRotationController.isTargetToken(statusBar.mToken)); + assertShouldFreezeInsetsPosition(asyncRotationController, navBar, false); // Navigation bar finishes drawing after the start transaction, so its fade-in animation // can execute directly. @@ -782,7 +783,7 @@ public class TransitionTests extends WindowTestsBase { final AsyncRotationController asyncRotationController = mDisplayContent.getAsyncRotationController(); assertNotNull(asyncRotationController); - assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar)); + assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true); statusBar.setOrientationChanging(true); player.startTransition(); @@ -828,7 +829,7 @@ public class TransitionTests extends WindowTestsBase { final AsyncRotationController asyncRotationController = mDisplayContent.getAsyncRotationController(); assertNotNull(asyncRotationController); - assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar)); + assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true); assertTrue(app.getTask().inTransition()); player.start(); @@ -863,6 +864,15 @@ public class TransitionTests extends WindowTestsBase { assertNull(mDisplayContent.getAsyncRotationController()); } + private static void assertShouldFreezeInsetsPosition(AsyncRotationController controller, + WindowState w, boolean freeze) { + if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) { + // Non blast sync should never freeze insets position. + freeze = false; + } + assertEquals(freeze, controller.shouldFreezeInsetsPosition(w)); + } + @Test public void testDeferRotationForTransientLaunch() { final TestTransitionPlayer player = registerTestTransitionPlayer(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 4293d487bcc2..9c9f5db68d09 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -242,7 +242,7 @@ class WindowTestsBase extends SystemServiceTestsBase { // Ensure letterbox vertical position multiplier is not overridden on any device target. // {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}, // may be set on some device form factors. - mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); + mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.0f); // Ensure letterbox horizontal reachability treatment isn't overridden on any device target. // {@link com.android.internal.R.bool.config_letterboxIsHorizontalReachabilityEnabled}, // may be set on some device form factors. diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 4b9b4a9781de..4ee066c5c211 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -1284,12 +1284,13 @@ public class VoiceInteractionManagerService extends SystemService { } } - @android.annotation.EnforcePermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) @Override public void startListeningFromMic( AudioFormat audioFormat, IMicrophoneHotwordDetectionVoiceInteractionCallback callback) throws RemoteException { + enforceCallingPermission(Manifest.permission.RECORD_AUDIO); + enforceCallingPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD); synchronized (this) { enforceIsCurrentVoiceInteractionService(); @@ -1349,12 +1350,13 @@ public class VoiceInteractionManagerService extends SystemService { } } - @android.annotation.EnforcePermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) @Override public void triggerHardwareRecognitionEventForTest( SoundTrigger.KeyphraseRecognitionEvent event, IHotwordRecognitionStatusCallback callback) throws RemoteException { + enforceCallingPermission(Manifest.permission.RECORD_AUDIO); + enforceCallingPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD); synchronized (this) { enforceIsCurrentVoiceInteractionService(); diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java index e7d95e4f53b3..58974eec838b 100644 --- a/telephony/java/android/telephony/AnomalyReporter.java +++ b/telephony/java/android/telephony/AnomalyReporter.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.ParcelUuid; +import android.provider.DeviceConfig; import com.android.internal.telephony.TelephonyStatsLog; import com.android.internal.util.IndentingPrintWriter; @@ -57,6 +58,9 @@ import java.util.concurrent.ConcurrentHashMap; public final class AnomalyReporter { private static final String TAG = "AnomalyReporter"; + private static final String KEY_IS_TELEPHONY_ANOMALY_REPORT_ENABLED = + "is_telephony_anomaly_report_enabled"; + private static Context sContext = null; private static Map<UUID, Integer> sEvents = new ConcurrentHashMap<>(); @@ -101,6 +105,12 @@ public final class AnomalyReporter { * @param carrierId the carrier of the id associated with this event. */ public static void reportAnomaly(@NonNull UUID eventId, String description, int carrierId) { + // Don't report if the server-side flag isn't loaded, as it implies other anomaly report + // related config hasn't loaded. + boolean isAnomalyReportEnabledFromServer = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_TELEPHONY, KEY_IS_TELEPHONY_ANOMALY_REPORT_ENABLED, false); + if (!isAnomalyReportEnabledFromServer) return; + if (sContext == null) { Rlog.w(TAG, "AnomalyReporter not yet initialized, dropping event=" + eventId); return; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 16f8fd913773..b6944eb17b0c 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -489,7 +489,10 @@ public class CarrierConfigManager { /** * Control whether users receive a simplified network settings UI and improved network * selection. + * + * @deprecated Never implemented. Has no behavior impact when override. DO NOT USE. */ + @Deprecated public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool"; @@ -8666,7 +8669,7 @@ public class CarrierConfigManager { * or EIMS--> * <item value="source=EUTRAN, target=IWLAN, type=disallowed, capabilities=IMS|EIMS"/> * <!-- Handover is always allowed in any condition. --> - * <item value="source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, + * <item value="source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN|UNKNOWN, * target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"/> * </string-array> * diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index cb985bf2cda5..0109ae614130 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -18,6 +18,7 @@ package android.telephony; import static android.text.TextUtils.formatSimple; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; @@ -37,6 +38,9 @@ import android.os.Build; import android.os.Parcel; import android.os.ParcelUuid; import android.os.Parcelable; +import android.telephony.SubscriptionManager.ProfileClass; +import android.telephony.SubscriptionManager.SimDisplayNameSource; +import android.telephony.SubscriptionManager.SubscriptionType; import android.telephony.SubscriptionManager.UsageSetting; import android.text.TextUtils; import android.util.DisplayMetrics; @@ -55,7 +59,6 @@ import java.util.Objects; * A Parcelable class for Subscription Information. */ public class SubscriptionInfo implements Parcelable { - /** * Size of text to render on the icon. */ @@ -65,162 +68,180 @@ public class SubscriptionInfo implements Parcelable { * Subscription Identifier, this is a device unique number * and not an index into an array */ - private int mId; + private final int mId; /** - * The GID for a SIM that maybe associated with this subscription, empty if unknown + * The ICCID of the SIM that is associated with this subscription, empty if unknown. */ - private String mIccId; + @NonNull + private final String mIccId; /** - * The index of the slot that currently contains the subscription - * and not necessarily unique and maybe INVALID_SLOT_ID if unknown + * The index of the SIM slot that currently contains the subscription and not necessarily unique + * and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or the subscription + * is inactive. */ - private int mSimSlotIndex; + private final int mSimSlotIndex; /** - * The name displayed to the user that identifies this subscription + * The name displayed to the user that identifies this subscription. This name is used + * in Settings page and can be renamed by the user. */ - private CharSequence mDisplayName; + @NonNull + private final CharSequence mDisplayName; /** - * String that identifies SPN/PLMN - * TODO : Add a new field that identifies only SPN for a sim + * The name displayed to the user that identifies subscription provider name. This name is the + * SPN displayed in status bar and many other places. Can't be renamed by the user. */ - private CharSequence mCarrierName; + @NonNull + private final CharSequence mCarrierName; /** * The subscription carrier id. + * * @see TelephonyManager#getSimCarrierId() */ - private int mCarrierId; + private final int mCarrierId; /** - * The source of the name, NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN, - * NAME_SOURCE_SIM_PNN, or NAME_SOURCE_USER_INPUT. + * The source of the {@link #mCarrierName}. */ - private int mNameSource; + @SimDisplayNameSource + private final int mNameSource; /** - * The color to be used for tinting the icon when displaying to the user + * The color to be used for tinting the icon when displaying to the user. */ - private int mIconTint; + private final int mIconTint; /** - * A number presented to the user identify this subscription + * The number presented to the user identify this subscription. */ - private String mNumber; + @NonNull + private final String mNumber; /** - * Data roaming state, DATA_ROAMING_ENABLE, DATA_ROAMING_DISABLE + * Whether user enables data roaming for this subscription or not. Either + * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or + * {@link SubscriptionManager#DATA_ROAMING_DISABLE}. */ - private int mDataRoaming; + private final int mDataRoaming; /** - * SIM icon bitmap cache + * SIM icon bitmap cache. */ - @Nullable private Bitmap mIconBitmap; + @Nullable + private Bitmap mIconBitmap; /** - * Mobile Country Code + * Mobile Country Code. */ - private String mMcc; + @Nullable + private final String mMcc; /** - * Mobile Network Code + * Mobile Network Code. */ - private String mMnc; + @Nullable + private final String mMnc; /** - * EHPLMNs associated with the subscription + * EHPLMNs associated with the subscription. */ - private String[] mEhplmns; + @NonNull + private final String[] mEhplmns; /** - * HPLMNs associated with the subscription + * HPLMNs associated with the subscription. */ - private String[] mHplmns; + @NonNull + private final String[] mHplmns; /** - * ISO Country code for the subscription's provider + * ISO Country code for the subscription's provider. */ - private String mCountryIso; + @NonNull + private final String mCountryIso; /** - * Whether the subscription is an embedded one. + * Whether the subscription is from eSIM. */ - private boolean mIsEmbedded; + private final boolean mIsEmbedded; /** - * The access rules for this subscription, if it is embedded and defines any. - * This does not include access rules for non-embedded subscriptions. + * The access rules for this subscription, if it is embedded and defines any. This does not + * include access rules for non-embedded subscriptions. */ @Nullable - private UiccAccessRule[] mNativeAccessRules; + private final UiccAccessRule[] mNativeAccessRules; /** * The carrier certificates for this subscription that are saved in carrier configs. * This does not include access rules from the Uicc, whether embedded or non-embedded. */ @Nullable - private UiccAccessRule[] mCarrierConfigAccessRules; + private final UiccAccessRule[] mCarrierConfigAccessRules; /** * The string ID of the SIM card. It is the ICCID of the active profile for a UICC card and the * EID for an eUICC card. */ - private String mCardString; + @NonNull + private final String mCardString; /** - * The card ID of the SIM card. This maps uniquely to the card string. + * The card ID of the SIM card. This maps uniquely to {@link #mCardString}. */ - private int mCardId; + private final int mCardId; /** * Whether the subscription is opportunistic. */ - private boolean mIsOpportunistic; + private final boolean mIsOpportunistic; /** - * A UUID assigned to the subscription group. It returns null if not assigned. - * Check {@link SubscriptionManager#createSubscriptionGroup(List)} for more details. + * A UUID assigned to the subscription group. {@code null} if not assigned. + * + * @see SubscriptionManager#createSubscriptionGroup(List) */ @Nullable - private ParcelUuid mGroupUUID; + private final ParcelUuid mGroupUuid; /** - * A package name that specifies who created the group. Null if mGroupUUID is null. + * A package name that specifies who created the group. Empty if not available. */ - private String mGroupOwner; + @NonNull + private final String mGroupOwner; /** - * Whether group of the subscription is disabled. - * This is only useful if it's a grouped opportunistic subscription. In this case, if all - * primary (non-opportunistic) subscriptions in the group are deactivated (unplugged pSIM - * or deactivated eSIM profile), we should disable this opportunistic subscription. + * Whether group of the subscription is disabled. This is only useful if it's a grouped + * opportunistic subscription. In this case, if all primary (non-opportunistic) subscriptions + * in the group are deactivated (unplugged pSIM or deactivated eSIM profile), we should disable + * this opportunistic subscription. */ - private boolean mIsGroupDisabled = false; + private final boolean mIsGroupDisabled; /** - * Profile class, PROFILE_CLASS_TESTING, PROFILE_CLASS_OPERATIONAL - * PROFILE_CLASS_PROVISIONING, or PROFILE_CLASS_UNSET. - * A profile on the eUICC can be defined as test, operational, provisioning, or unset. - * The profile class will be populated from the profile metadata if present. Otherwise, - * the profile class defaults to unset if there is no profile metadata or the subscription - * is not on an eUICC ({@link #isEmbedded} returns false). + * The profile class populated from the profile metadata if present. Otherwise, + * the profile class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no + * profile metadata or the subscription is not on an eUICC ({@link #isEmbedded} returns + * {@code false}). */ - private int mProfileClass; + @ProfileClass + private final int mProfileClass; /** - * Type of subscription + * Type of the subscription. */ - private int mSubscriptionType; + @SubscriptionType + private final int mType; /** * Whether uicc applications are configured to enable or disable. * By default it's true. */ - private boolean mAreUiccApplicationsEnabled = true; + private final boolean mAreUiccApplicationsEnabled; /** * The port index of the Uicc card. @@ -230,25 +251,16 @@ public class SubscriptionInfo implements Parcelable { /** * Subscription's preferred usage setting. */ - private @UsageSetting int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN; - - /** - * Public copy constructor. - * @hide - */ - public SubscriptionInfo(SubscriptionInfo info) { - this(info.mId, info.mIccId, info.mSimSlotIndex, info.mDisplayName, info.mCarrierName, - info.mNameSource, info.mIconTint, info.mNumber, info.mDataRoaming, info.mIconBitmap, - info.mMcc, info.mMnc, info.mCountryIso, info.mIsEmbedded, info.mNativeAccessRules, - info.mCardString, info.mCardId, info.mIsOpportunistic, - info.mGroupUUID == null ? null : info.mGroupUUID.toString(), info.mIsGroupDisabled, - info.mCarrierId, info.mProfileClass, info.mSubscriptionType, info.mGroupOwner, - info.mCarrierConfigAccessRules, info.mAreUiccApplicationsEnabled); - } + @UsageSetting + private final int mUsageSetting; /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @@ -262,7 +274,11 @@ public class SubscriptionInfo implements Parcelable { /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @@ -276,7 +292,11 @@ public class SubscriptionInfo implements Parcelable { /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @@ -293,7 +313,11 @@ public class SubscriptionInfo implements Parcelable { /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @@ -311,49 +335,94 @@ public class SubscriptionInfo implements Parcelable { /** * @hide + * + * @deprecated Use {@link SubscriptionInfo.Builder}. */ + // TODO: Clean up after external usages moved to builder model. + @Deprecated public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId, - boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled, + boolean isOpportunistic, @Nullable String groupUuid, boolean isGroupDisabled, int carrierId, int profileClass, int subType, @Nullable String groupOwner, @Nullable UiccAccessRule[] carrierConfigAccessRules, boolean areUiccApplicationsEnabled, int portIndex, @UsageSetting int usageSetting) { this.mId = id; this.mIccId = iccId; this.mSimSlotIndex = simSlotIndex; - this.mDisplayName = displayName; + this.mDisplayName = displayName; this.mCarrierName = carrierName; this.mNameSource = nameSource; this.mIconTint = iconTint; this.mNumber = number; this.mDataRoaming = roaming; this.mIconBitmap = icon; - this.mMcc = mcc; - this.mMnc = mnc; - this.mCountryIso = countryIso; + this.mMcc = TextUtils.emptyIfNull(mcc); + this.mMnc = TextUtils.emptyIfNull(mnc); + this.mHplmns = null; + this.mEhplmns = null; + this.mCountryIso = TextUtils.emptyIfNull(countryIso); this.mIsEmbedded = isEmbedded; this.mNativeAccessRules = nativeAccessRules; - this.mCardString = cardString; + this.mCardString = TextUtils.emptyIfNull(cardString); this.mCardId = cardId; this.mIsOpportunistic = isOpportunistic; - this.mGroupUUID = groupUUID == null ? null : ParcelUuid.fromString(groupUUID); + this.mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid); this.mIsGroupDisabled = isGroupDisabled; this.mCarrierId = carrierId; this.mProfileClass = profileClass; - this.mSubscriptionType = subType; - this.mGroupOwner = groupOwner; + this.mType = subType; + this.mGroupOwner = TextUtils.emptyIfNull(groupOwner); this.mCarrierConfigAccessRules = carrierConfigAccessRules; this.mAreUiccApplicationsEnabled = areUiccApplicationsEnabled; this.mPortIndex = portIndex; this.mUsageSetting = usageSetting; } + /** - * @return the subscription ID. + * Constructor from builder. + * + * @param builder Builder of {@link SubscriptionInfo}. + */ + private SubscriptionInfo(@NonNull Builder builder) { + this.mId = builder.mId; + this.mIccId = builder.mIccId; + this.mSimSlotIndex = builder.mSimSlotIndex; + this.mDisplayName = builder.mDisplayName; + this.mCarrierName = builder.mCarrierName; + this.mNameSource = builder.mNameSource; + this.mIconTint = builder.mIconTint; + this.mNumber = builder.mNumber; + this.mDataRoaming = builder.mDataRoaming; + this.mIconBitmap = builder.mIconBitmap; + this.mMcc = builder.mMcc; + this.mMnc = builder.mMnc; + this.mEhplmns = builder.mEhplmns; + this.mHplmns = builder.mHplmns; + this.mCountryIso = builder.mCountryIso; + this.mIsEmbedded = builder.mIsEmbedded; + this.mNativeAccessRules = builder.mNativeAccessRules; + this.mCardString = builder.mCardString; + this.mCardId = builder.mCardId; + this.mIsOpportunistic = builder.mIsOpportunistic; + this.mGroupUuid = builder.mGroupUuid; + this.mIsGroupDisabled = builder.mIsGroupDisabled; + this.mCarrierId = builder.mCarrierId; + this.mProfileClass = builder.mProfileClass; + this.mType = builder.mType; + this.mGroupOwner = builder.mGroupOwner; + this.mCarrierConfigAccessRules = builder.mCarrierConfigAccessRules; + this.mAreUiccApplicationsEnabled = builder.mAreUiccApplicationsEnabled; + this.mPortIndex = builder.mPortIndex; + this.mUsageSetting = builder.mUsageSetting; + } + + /** + * @return The subscription ID. */ public int getSubscriptionId() { - return this.mId; + return mId; } /** @@ -370,78 +439,56 @@ public class SubscriptionInfo implements Parcelable { * @return the ICC ID, or an empty string if one of these requirements is not met */ public String getIccId() { - return this.mIccId; + return mIccId; } /** - * @hide - */ - public void clearIccId() { - this.mIccId = ""; - } - - /** - * @return the slot index of this Subscription's SIM card. + * @return The index of the SIM slot that currently contains the subscription and not + * necessarily unique and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or + * the subscription is inactive. */ public int getSimSlotIndex() { - return this.mSimSlotIndex; + return mSimSlotIndex; } /** - * @return the carrier id of this Subscription carrier. + * @return The carrier id of this subscription carrier. + * * @see TelephonyManager#getSimCarrierId() */ public int getCarrierId() { - return this.mCarrierId; + return mCarrierId; } /** - * @return the name displayed to the user that identifies this subscription + * @return The name displayed to the user that identifies this subscription. This name is + * used in Settings page and can be renamed by the user. + * + * @see #getCarrierName() */ public CharSequence getDisplayName() { - return this.mDisplayName; + return mDisplayName; } /** - * Sets the name displayed to the user that identifies this subscription - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void setDisplayName(CharSequence name) { - this.mDisplayName = name; - } - - /** - * @return the name displayed to the user that identifies Subscription provider name + * @return The name displayed to the user that identifies subscription provider name. This name + * is the SPN displayed in status bar and many other places. Can't be renamed by the user. + * + * @see #getDisplayName() */ public CharSequence getCarrierName() { - return this.mCarrierName; - } - - /** - * Sets the name displayed to the user that identifies Subscription provider name - * @hide - */ - public void setCarrierName(CharSequence name) { - this.mCarrierName = name; + return mCarrierName; } /** - * @return the source of the name, eg NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN or - * NAME_SOURCE_USER_INPUT. + * @return The source of the {@link #getCarrierName()}. + * * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @SimDisplayNameSource public int getNameSource() { - return this.mNameSource; - } - - /** - * @hide - */ - public void setAssociatedPlmns(String[] ehplmns, String[] hplmns) { - mEhplmns = ehplmns; - mHplmns = hplmns; + return mNameSource; } /** @@ -499,15 +546,6 @@ public class SubscriptionInfo implements Parcelable { } /** - * Sets the color displayed to the user that identifies this subscription - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void setIconTint(int iconTint) { - this.mIconTint = iconTint; - } - - /** * Returns the number of this subscription. * * Starting with API level 30, returns the number of this subscription if the calling app meets @@ -533,28 +571,23 @@ public class SubscriptionInfo implements Parcelable { } /** - * @hide - */ - public void clearNumber() { - mNumber = ""; - } - - /** - * @return the data roaming state for this subscription, either - * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or {@link SubscriptionManager#DATA_ROAMING_DISABLE}. + * Whether user enables data roaming for this subscription or not. Either + * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or + * {@link SubscriptionManager#DATA_ROAMING_DISABLE}. */ public int getDataRoaming() { - return this.mDataRoaming; + return mDataRoaming; } /** - * @return the MCC. + * @return The mobile country code. + * * @deprecated Use {@link #getMccString()} instead. */ @Deprecated public int getMcc() { try { - return this.mMcc == null ? 0 : Integer.valueOf(this.mMcc); + return mMcc == null ? 0 : Integer.parseInt(mMcc); } catch (NumberFormatException e) { Log.w(SubscriptionInfo.class.getSimpleName(), "MCC string is not a number"); return 0; @@ -562,13 +595,14 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return the MNC. + * @return The mobile network code. + * * @deprecated Use {@link #getMncString()} instead. */ @Deprecated public int getMnc() { try { - return this.mMnc == null ? 0 : Integer.valueOf(this.mMnc); + return mMnc == null ? 0 : Integer.parseInt(mMnc); } catch (NumberFormatException e) { Log.w(SubscriptionInfo.class.getSimpleName(), "MNC string is not a number"); return 0; @@ -576,36 +610,40 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return The MCC, as a string. + * @return The mobile country code. */ - public @Nullable String getMccString() { - return this.mMcc; + @Nullable + public String getMccString() { + return mMcc; } /** - * @return The MNC, as a string. + * @return The mobile network code. */ - public @Nullable String getMncString() { - return this.mMnc; + @Nullable + public String getMncString() { + return mMnc; } /** - * @return the ISO country code + * @return The ISO country code. Empty if not available. */ public String getCountryIso() { - return this.mCountryIso; + return mCountryIso; } - /** @return whether the subscription is an eUICC one. */ + /** + * @return {@code true} if the subscription is from eSIM. + */ public boolean isEmbedded() { - return this.mIsEmbedded; + return mIsEmbedded; } /** * An opportunistic subscription connects to a network that is * limited in functionality and / or coverage. * - * @return whether subscription is opportunistic. + * @return Whether subscription is opportunistic. */ public boolean isOpportunistic() { return mIsOpportunistic; @@ -617,23 +655,18 @@ public class SubscriptionInfo implements Parcelable { * Such that those subscriptions will have some affiliated behaviors such as opportunistic * subscription may be invisible to the user. * - * @return group UUID a String of group UUID if it belongs to a group. Otherwise - * it will return null. + * @return Group UUID a String of group UUID if it belongs to a group. Otherwise + * {@code null}. */ - public @Nullable ParcelUuid getGroupUuid() { - return mGroupUUID; - } - - /** - * @hide - */ - public void clearGroupUuid() { - this.mGroupUUID = null; + @Nullable + public ParcelUuid getGroupUuid() { + return mGroupUuid; } /** * @hide */ + @NonNull public List<String> getEhplmns() { return mEhplmns == null ? Collections.emptyList() : Arrays.asList(mEhplmns); } @@ -641,36 +674,45 @@ public class SubscriptionInfo implements Parcelable { /** * @hide */ + @NonNull public List<String> getHplmns() { return mHplmns == null ? Collections.emptyList() : Arrays.asList(mHplmns); } /** - * Return owner package of group the subscription belongs to. + * @return The owner package of group the subscription belongs to. * * @hide */ - public @Nullable String getGroupOwner() { + @NonNull + public String getGroupOwner() { return mGroupOwner; } /** - * @return the profile class of this subscription. + * @return The profile class populated from the profile metadata if present. Otherwise, + * the profile class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no + * profile metadata or the subscription is not on an eUICC ({@link #isEmbedded} return + * {@code false}). + * * @hide */ @SystemApi - public @SubscriptionManager.ProfileClass int getProfileClass() { - return this.mProfileClass; + @ProfileClass + public int getProfileClass() { + return mProfileClass; } /** * This method returns the type of a subscription. It can be * {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or * {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}. - * @return the type of subscription + * + * @return The type of the subscription. */ - public @SubscriptionManager.SubscriptionType int getSubscriptionType() { - return mSubscriptionType; + @SubscriptionType + public int getSubscriptionType() { + return mType; } /** @@ -679,7 +721,7 @@ public class SubscriptionInfo implements Parcelable { * returns true). * * @param context Context of the application to check. - * @return whether the app is authorized to manage this subscription per its metadata. + * @return Whether the app is authorized to manage this subscription per its metadata. * @hide * @deprecated - Do not use. */ @@ -700,7 +742,7 @@ public class SubscriptionInfo implements Parcelable { */ @Deprecated public boolean canManageSubscription(Context context, String packageName) { - List<UiccAccessRule> allAccessRules = getAllAccessRules(); + List<UiccAccessRule> allAccessRules = getAccessRules(); if (allAccessRules == null) { return false; } @@ -723,24 +765,14 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return the {@link UiccAccessRule}s that are stored in Uicc, dictating who - * is authorized to manage this subscription. - * TODO and fix it properly in R / master: either deprecate this and have 3 APIs - * native + carrier + all, or have this return all by default. + * @return The {@link UiccAccessRule}s that are stored in Uicc, dictating who is authorized to + * manage this subscription. + * * @hide */ @SystemApi - public @Nullable List<UiccAccessRule> getAccessRules() { - if (mNativeAccessRules == null) return null; - return Arrays.asList(mNativeAccessRules); - } - - /** - * @return the {@link UiccAccessRule}s that are both stored on Uicc and in carrierConfigs - * dictating who is authorized to manage this subscription. - * @hide - */ - public @Nullable List<UiccAccessRule> getAllAccessRules() { + @Nullable + public List<UiccAccessRule> getAccessRules() { List<UiccAccessRule> merged = new ArrayList<>(); if (mNativeAccessRules != null) { merged.addAll(getAccessRules()); @@ -762,50 +794,38 @@ public class SubscriptionInfo implements Parcelable { * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile * owner access is deprecated and will be removed in a future release. * - * @return the card string of the SIM card which contains the subscription or an empty string + * @return The card string of the SIM card which contains the subscription or an empty string * if these requirements are not met. The card string is the ICCID for UICCs or the EID for * eUICCs. + * * @hide - * //TODO rename usages in LPA: UiccSlotUtil.java, UiccSlotsManager.java, UiccSlotInfoTest.java */ + @NonNull public String getCardString() { - return this.mCardString; + return mCardString; } /** - * @hide - */ - public void clearCardString() { - this.mCardString = ""; - } - - /** - * Returns the card ID of the SIM card which contains the subscription (see - * {@link UiccCardInfo#getCardId()}. - * @return the cardId + * @return The card ID of the SIM card which contains the subscription. + * + * @see UiccCardInfo#getCardId(). */ public int getCardId() { - return this.mCardId; + return mCardId; } /** - * Returns the port index of the SIM card which contains the subscription. - * - * @return the portIndex + * @return The port index of the SIM card which contains the subscription. */ public int getPortIndex() { - return this.mPortIndex; - } - - /** - * Set whether the subscription's group is disabled. - * @hide - */ - public void setGroupDisabled(boolean isGroupDisabled) { - this.mIsGroupDisabled = isGroupDisabled; + return mPortIndex; } /** - * Return whether the subscription's group is disabled. + * @return {@code true} if the group of the subscription is disabled. This is only useful if + * it's a grouped opportunistic subscription. In this case, if all primary (non-opportunistic) + * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile), we + * should disable this opportunistic subscription. + * * @hide */ @SystemApi @@ -814,7 +834,7 @@ public class SubscriptionInfo implements Parcelable { } /** - * Return whether uicc applications are set to be enabled or disabled. + * @return {@code true} if Uicc applications are set to be enabled or disabled. * @hide */ @SystemApi @@ -825,56 +845,50 @@ public class SubscriptionInfo implements Parcelable { /** * Get the usage setting for this subscription. * - * @return the usage setting used for this subscription. + * @return The usage setting used for this subscription. */ - public @UsageSetting int getUsageSetting() { + @UsageSetting + public int getUsageSetting() { return mUsageSetting; } - public static final @android.annotation.NonNull - Parcelable.Creator<SubscriptionInfo> CREATOR = - new Parcelable.Creator<SubscriptionInfo>() { + @NonNull + public static final Parcelable.Creator<SubscriptionInfo> CREATOR = + new Parcelable.Creator<SubscriptionInfo>() { @Override public SubscriptionInfo createFromParcel(Parcel source) { - int id = source.readInt(); - String iccId = source.readString(); - int simSlotIndex = source.readInt(); - CharSequence displayName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - CharSequence carrierName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - int nameSource = source.readInt(); - int iconTint = source.readInt(); - String number = source.readString(); - int dataRoaming = source.readInt(); - String mcc = source.readString(); - String mnc = source.readString(); - String countryIso = source.readString(); - boolean isEmbedded = source.readBoolean(); - UiccAccessRule[] nativeAccessRules = source.createTypedArray(UiccAccessRule.CREATOR); - String cardString = source.readString(); - int cardId = source.readInt(); - int portId = source.readInt(); - boolean isOpportunistic = source.readBoolean(); - String groupUUID = source.readString(); - boolean isGroupDisabled = source.readBoolean(); - int carrierid = source.readInt(); - int profileClass = source.readInt(); - int subType = source.readInt(); - String[] ehplmns = source.createStringArray(); - String[] hplmns = source.createStringArray(); - String groupOwner = source.readString(); - UiccAccessRule[] carrierConfigAccessRules = source.createTypedArray( - UiccAccessRule.CREATOR); - boolean areUiccApplicationsEnabled = source.readBoolean(); - int usageSetting = source.readInt(); - - SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName, - carrierName, nameSource, iconTint, number, dataRoaming, /* icon= */ null, - mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, cardId, - isOpportunistic, groupUUID, isGroupDisabled, carrierid, profileClass, subType, - groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled, - portId, usageSetting); - info.setAssociatedPlmns(ehplmns, hplmns); - return info; + return new Builder() + .setId(source.readInt()) + .setIccId(source.readString()) + .setSimSlotIndex(source.readInt()) + .setDisplayName(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source)) + .setCarrierName(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source)) + .setNameSource(source.readInt()) + .setIconTint(source.readInt()) + .setNumber(source.readString()) + .setDataRoaming(source.readInt()) + .setMcc(source.readString()) + .setMnc(source.readString()) + .setCountryIso(source.readString()) + .setEmbedded(source.readBoolean()) + .setNativeAccessRules(source.createTypedArray(UiccAccessRule.CREATOR)) + .setCardString(source.readString()) + .setCardId(source.readInt()) + .setPortIndex(source.readInt()) + .setOpportunistic(source.readBoolean()) + .setGroupUuid(source.readString8()) + .setGroupDisabled(source.readBoolean()) + .setCarrierId(source.readInt()) + .setProfileClass(source.readInt()) + .setType(source.readInt()) + .setEhplmns(source.createStringArray()) + .setHplmns(source.createStringArray()) + .setGroupOwner(source.readString()) + .setCarrierConfigAccessRules(source.createTypedArray( + UiccAccessRule.CREATOR)) + .setUiccApplicationsEnabled(source.readBoolean()) + .setUsageSetting(source.readInt()) + .build(); } @Override @@ -904,11 +918,11 @@ public class SubscriptionInfo implements Parcelable { dest.writeInt(mCardId); dest.writeInt(mPortIndex); dest.writeBoolean(mIsOpportunistic); - dest.writeString(mGroupUUID == null ? null : mGroupUUID.toString()); + dest.writeString8(mGroupUuid == null ? null : mGroupUuid.toString()); dest.writeBoolean(mIsGroupDisabled); dest.writeInt(mCarrierId); dest.writeInt(mProfileClass); - dest.writeInt(mSubscriptionType); + dest.writeInt(mType); dest.writeStringArray(mEhplmns); dest.writeStringArray(mHplmns); dest.writeString(mGroupOwner); @@ -923,6 +937,11 @@ public class SubscriptionInfo implements Parcelable { } /** + * Get ICCID stripped PII information on user build. + * + * @param iccId The original ICCID. + * @return The stripped string. + * * @hide */ public static String givePrintableIccid(String iccId) { @@ -951,12 +970,12 @@ public class SubscriptionInfo implements Parcelable { + " nativeAccessRules=" + Arrays.toString(mNativeAccessRules) + " cardString=" + cardStringToPrint + " cardId=" + mCardId + " portIndex=" + mPortIndex - + " isOpportunistic=" + mIsOpportunistic + " groupUUID=" + mGroupUUID + + " isOpportunistic=" + mIsOpportunistic + " groupUuid=" + mGroupUuid + " isGroupDisabled=" + mIsGroupDisabled + " profileClass=" + mProfileClass + " ehplmns=" + Arrays.toString(mEhplmns) + " hplmns=" + Arrays.toString(mHplmns) - + " subscriptionType=" + mSubscriptionType + + " mType=" + mType + " groupOwner=" + mGroupOwner + " carrierConfigAccessRules=" + Arrays.toString(mCarrierConfigAccessRules) + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled @@ -966,7 +985,7 @@ public class SubscriptionInfo implements Parcelable { @Override public int hashCode() { return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded, - mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString, + mIsOpportunistic, mGroupUuid, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, Arrays.hashCode(mNativeAccessRules), mIsGroupDisabled, mCarrierId, mProfileClass, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting); @@ -974,16 +993,9 @@ public class SubscriptionInfo implements Parcelable { @Override public boolean equals(Object obj) { - if (obj == null) return false; - if (obj == this) return true; - - SubscriptionInfo toCompare; - try { - toCompare = (SubscriptionInfo) obj; - } catch (ClassCastException ex) { - return false; - } - + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + SubscriptionInfo toCompare = (SubscriptionInfo) obj; return mId == toCompare.mId && mSimSlotIndex == toCompare.mSimSlotIndex && mNameSource == toCompare.mNameSource @@ -994,7 +1006,7 @@ public class SubscriptionInfo implements Parcelable { && mIsGroupDisabled == toCompare.mIsGroupDisabled && mAreUiccApplicationsEnabled == toCompare.mAreUiccApplicationsEnabled && mCarrierId == toCompare.mCarrierId - && Objects.equals(mGroupUUID, toCompare.mGroupUUID) + && Objects.equals(mGroupUuid, toCompare.mGroupUuid) && Objects.equals(mIccId, toCompare.mIccId) && Objects.equals(mNumber, toCompare.mNumber) && Objects.equals(mMcc, toCompare.mMcc) @@ -1012,4 +1024,629 @@ public class SubscriptionInfo implements Parcelable { && Arrays.equals(mHplmns, toCompare.mHplmns) && mUsageSetting == toCompare.mUsageSetting; } + + /** + * The builder class of {@link SubscriptionInfo}. + * + * @hide + */ + public static class Builder { + /** + * The subscription id. + */ + private int mId = 0; + + /** + * The ICCID of the SIM that is associated with this subscription, empty if unknown. + */ + @NonNull + private String mIccId = ""; + + /** + * The index of the SIM slot that currently contains the subscription and not necessarily + * unique and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or the + * subscription is inactive. + */ + private int mSimSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX; + + /** + * The name displayed to the user that identifies this subscription. This name is used + * in Settings page and can be renamed by the user. + */ + @NonNull + private CharSequence mDisplayName = ""; + + /** + * The name displayed to the user that identifies subscription provider name. This name + * is the SPN displayed in status bar and many other places. Can't be renamed by the user. + */ + @NonNull + private CharSequence mCarrierName = ""; + + /** + * The source of the carrier name. + */ + @SimDisplayNameSource + private int mNameSource = SubscriptionManager.NAME_SOURCE_CARRIER_ID; + + /** + * The color to be used for tinting the icon when displaying to the user. + */ + private int mIconTint = 0; + + /** + * The number presented to the user identify this subscription. + */ + @NonNull + private String mNumber = ""; + + /** + * Whether user enables data roaming for this subscription or not. Either + * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or + * {@link SubscriptionManager#DATA_ROAMING_DISABLE}. + */ + private int mDataRoaming = SubscriptionManager.DATA_ROAMING_DISABLE; + + /** + * SIM icon bitmap cache. + */ + @Nullable + private Bitmap mIconBitmap = null; + + /** + * The mobile country code. + */ + @Nullable + private String mMcc = null; + + /** + * The mobile network code. + */ + @Nullable + private String mMnc = null; + + /** + * EHPLMNs associated with the subscription. + */ + @NonNull + private String[] mEhplmns = new String[0]; + + /** + * HPLMNs associated with the subscription. + */ + @NonNull + private String[] mHplmns = new String[0]; + + /** + * The ISO Country code for the subscription's provider. + */ + @NonNull + private String mCountryIso = ""; + + /** + * Whether the subscription is from eSIM. + */ + private boolean mIsEmbedded = false; + + /** + * The native access rules for this subscription, if it is embedded and defines any. This + * does not include access rules for non-embedded subscriptions. + */ + @Nullable + private UiccAccessRule[] mNativeAccessRules = null; + + /** + * The card string of the SIM card. + */ + @NonNull + private String mCardString = ""; + + /** + * The card ID of the SIM card which contains the subscription. + */ + private int mCardId = -1; + + /** + * Whether the subscription is opportunistic or not. + */ + private boolean mIsOpportunistic = false; + + /** + * The group UUID of the subscription group. + */ + @Nullable + private ParcelUuid mGroupUuid = null; + + /** + * Whether group of the subscription is disabled. This is only useful if it's a grouped + * opportunistic subscription. In this case, if all primary (non-opportunistic) + * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile), + * we should disable this opportunistic subscription. + */ + private boolean mIsGroupDisabled = false; + + /** + * The carrier id. + * + * @see TelephonyManager#getSimCarrierId() + */ + private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; + + /** + * The profile class populated from the profile metadata if present. Otherwise, the profile + * class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no profile + * metadata or the subscription is not on an eUICC ({@link #isEmbedded} returns + * {@code false}). + */ + @ProfileClass + private int mProfileClass = SubscriptionManager.PROFILE_CLASS_UNSET; + + /** + * The subscription type. + */ + @SubscriptionType + private int mType = SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM; + + /** + * The owner package of group the subscription belongs to. + */ + @NonNull + private String mGroupOwner = ""; + + /** + * The carrier certificates for this subscription that are saved in carrier configs. + * This does not include access rules from the Uicc, whether embedded or non-embedded. + */ + @Nullable + private UiccAccessRule[] mCarrierConfigAccessRules = null; + + /** + * Whether Uicc applications are configured to enable or not. + */ + private boolean mAreUiccApplicationsEnabled = true; + + /** + * the port index of the Uicc card. + */ + private int mPortIndex = 0; + + /** + * Subscription's preferred usage setting. + */ + @UsageSetting + private int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN; + + /** + * Default constructor. + */ + public Builder() { + } + + /** + * Constructor from {@link SubscriptionInfo}. + * + * @param info The subscription info. + */ + public Builder(@NonNull SubscriptionInfo info) { + mId = info.mId; + mIccId = info.mIccId; + mSimSlotIndex = info.mSimSlotIndex; + mDisplayName = info.mDisplayName; + mCarrierName = info.mCarrierName; + mNameSource = info.mNameSource; + mIconTint = info.mIconTint; + mNumber = info.mNumber; + mDataRoaming = info.mDataRoaming; + mIconBitmap = info.mIconBitmap; + mMcc = info.mMcc; + mMnc = info.mMnc; + mEhplmns = info.mEhplmns; + mHplmns = info.mHplmns; + mCountryIso = info.mCountryIso; + mIsEmbedded = info.mIsEmbedded; + mNativeAccessRules = info.mNativeAccessRules; + mCardString = info.mCardString; + mCardId = info.mCardId; + mIsOpportunistic = info.mIsOpportunistic; + mGroupUuid = info.mGroupUuid; + mIsGroupDisabled = info.mIsGroupDisabled; + mCarrierId = info.mCarrierId; + mProfileClass = info.mProfileClass; + mType = info.mType; + mGroupOwner = info.mGroupOwner; + mCarrierConfigAccessRules = info.mCarrierConfigAccessRules; + mAreUiccApplicationsEnabled = info.mAreUiccApplicationsEnabled; + mPortIndex = info.mPortIndex; + mUsageSetting = info.mUsageSetting; + } + + /** + * Set the subscription id. + * + * @param id The subscription id. + * @return The builder. + */ + @NonNull + public Builder setId(int id) { + mId = id; + return this; + } + + /** + * Set the ICCID of the SIM that is associated with this subscription. + * + * @param iccId The ICCID of the SIM that is associated with this subscription. + * @return The builder. + */ + @NonNull + public Builder setIccId(@Nullable String iccId) { + mIccId = TextUtils.emptyIfNull(iccId); + return this; + } + + /** + * Set the SIM index of the slot that currently contains the subscription. Set to + * {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if the subscription is inactive. + * + * @param simSlotIndex The SIM slot index. + * @return The builder. + */ + @NonNull + public Builder setSimSlotIndex(int simSlotIndex) { + mSimSlotIndex = simSlotIndex; + return this; + } + + /** + * The name displayed to the user that identifies this subscription. This name is used + * in Settings page and can be renamed by the user. + * + * @param displayName The display name. + * @return The builder. + */ + @NonNull + public Builder setDisplayName(@Nullable CharSequence displayName) { + mDisplayName = displayName == null ? "" : displayName; + return this; + } + + /** + * The name displayed to the user that identifies subscription provider name. This name + * is the SPN displayed in status bar and many other places. Can't be renamed by the user. + * + * @param carrierName The carrier name. + * @return The builder. + */ + @NonNull + public Builder setCarrierName(@Nullable CharSequence carrierName) { + mCarrierName = carrierName == null ? "" : carrierName; + return this; + } + + /** + * Set the source of the carrier name. + * + * @param nameSource The source of the carrier name. + * @return The builder. + */ + @NonNull + public Builder setNameSource(@SimDisplayNameSource int nameSource) { + mNameSource = nameSource; + return this; + } + + /** + * Set the color to be used for tinting the icon when displaying to the user. + * + * @param iconTint The color to be used for tinting the icon when displaying to the user. + * @return The builder. + */ + @NonNull + public Builder setIconTint(int iconTint) { + mIconTint = iconTint; + return this; + } + + /** + * Set the number presented to the user identify this subscription. + * + * @param number the number presented to the user identify this subscription. + * @return The builder. + */ + @NonNull + public Builder setNumber(@Nullable String number) { + mNumber = TextUtils.emptyIfNull(number); + return this; + } + + /** + * Set whether user enables data roaming for this subscription or not. + * + * @param dataRoaming Data roaming mode. Either + * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or + * {@link SubscriptionManager#DATA_ROAMING_DISABLE} + * @return The builder. + */ + @NonNull + public Builder setDataRoaming(int dataRoaming) { + mDataRoaming = dataRoaming; + return this; + } + + /** + * Set SIM icon bitmap cache. + * + * @param iconBitmap SIM icon bitmap cache. + * @return The builder. + */ + @NonNull + public Builder setIcon(@Nullable Bitmap iconBitmap) { + mIconBitmap = iconBitmap; + return this; + } + + /** + * Set the mobile country code. + * + * @param mcc The mobile country code. + * @return The builder. + */ + @NonNull + public Builder setMcc(@Nullable String mcc) { + mMcc = mcc; + return this; + } + + /** + * Set the mobile network code. + * + * @param mnc Mobile network code. + * @return The builder. + */ + @NonNull + public Builder setMnc(@Nullable String mnc) { + mMnc = mnc; + return this; + } + + /** + * Set EHPLMNs associated with the subscription. + * + * @param ehplmns EHPLMNs associated with the subscription. + * @return The builder. + */ + @NonNull + public Builder setEhplmns(@Nullable String[] ehplmns) { + mEhplmns = ehplmns == null ? new String[0] : ehplmns; + return this; + } + + /** + * Set HPLMNs associated with the subscription. + * + * @param hplmns HPLMNs associated with the subscription. + * @return The builder. + */ + @NonNull + public Builder setHplmns(@Nullable String[] hplmns) { + mHplmns = hplmns == null ? new String[0] : hplmns; + return this; + } + + /** + * Set the ISO Country code for the subscription's provider. + * + * @param countryIso The ISO Country code for the subscription's provider. + * @return The builder. + */ + @NonNull + public Builder setCountryIso(@Nullable String countryIso) { + mCountryIso = TextUtils.emptyIfNull(countryIso); + return this; + } + + /** + * Set whether the subscription is from eSIM or not. + * + * @param isEmbedded {@code true} if the subscription is from eSIM. + * @return The builder. + */ + @NonNull + public Builder setEmbedded(boolean isEmbedded) { + mIsEmbedded = isEmbedded; + return this; + } + + /** + * Set the native access rules for this subscription, if it is embedded and defines any. + * This does not include access rules for non-embedded subscriptions. + * + * @param nativeAccessRules The native access rules for this subscription. + * @return The builder. + */ + @NonNull + public Builder setNativeAccessRules(@Nullable UiccAccessRule[] nativeAccessRules) { + mNativeAccessRules = nativeAccessRules; + return this; + } + + /** + * Set the card string of the SIM card. + * + * @param cardString The card string of the SIM card. + * @return The builder. + * + * @see #getCardString() + */ + @NonNull + public Builder setCardString(@Nullable String cardString) { + mCardString = TextUtils.emptyIfNull(cardString); + return this; + } + + /** + * Set the card ID of the SIM card which contains the subscription. + * + * @param cardId The card ID of the SIM card which contains the subscription. + * @return The builder. + */ + @NonNull + public Builder setCardId(int cardId) { + mCardId = cardId; + return this; + } + + /** + * Set whether the subscription is opportunistic or not. + * + * @param isOpportunistic {@code true} if the subscription is opportunistic. + * @return The builder. + */ + @NonNull + public Builder setOpportunistic(boolean isOpportunistic) { + mIsOpportunistic = isOpportunistic; + return this; + } + + /** + * Set the group UUID of the subscription group. + * + * @param groupUuid The group UUID. + * @return The builder. + * + * @see #getGroupUuid() + */ + @NonNull + public Builder setGroupUuid(@Nullable String groupUuid) { + mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid); + return this; + } + + /** + * Whether group of the subscription is disabled. This is only useful if it's a grouped + * opportunistic subscription. In this case, if all primary (non-opportunistic) + * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile), + * we should disable this opportunistic subscription. + * + * @param isGroupDisabled {@code true} if group of the subscription is disabled. + * @return The builder. + */ + @NonNull + public Builder setGroupDisabled(boolean isGroupDisabled) { + mIsGroupDisabled = isGroupDisabled; + return this; + } + + /** + * Set the subscription carrier id. + * + * @param carrierId The carrier id. + * @return The builder + * + * @see TelephonyManager#getSimCarrierId() + */ + @NonNull + public Builder setCarrierId(int carrierId) { + mCarrierId = carrierId; + return this; + } + + /** + * Set the profile class populated from the profile metadata if present. + * + * @param profileClass the profile class populated from the profile metadata if present. + * @return The builder + * + * @see #getProfileClass() + */ + @NonNull + public Builder setProfileClass(@ProfileClass int profileClass) { + mProfileClass = profileClass; + return this; + } + + /** + * Set the subscription type. + * + * @param type Subscription type. + * @return The builder. + */ + @NonNull + public Builder setType(@SubscriptionType int type) { + mType = type; + return this; + } + + /** + * Set the owner package of group the subscription belongs to. + * + * @param groupOwner Owner package of group the subscription belongs to. + * @return The builder. + */ + @NonNull + public Builder setGroupOwner(@Nullable String groupOwner) { + mGroupOwner = TextUtils.emptyIfNull(groupOwner); + return this; + } + + /** + * Set the carrier certificates for this subscription that are saved in carrier configs. + * This does not include access rules from the Uicc, whether embedded or non-embedded. + * + * @param carrierConfigAccessRules The carrier certificates for this subscription + * @return The builder. + */ + @NonNull + public Builder setCarrierConfigAccessRules( + @Nullable UiccAccessRule[] carrierConfigAccessRules) { + mCarrierConfigAccessRules = carrierConfigAccessRules; + return this; + } + + /** + * Set whether Uicc applications are configured to enable or not. + * + * @param uiccApplicationsEnabled {@code true} if Uicc applications are configured to + * enable. + * @return The builder. + */ + @NonNull + public Builder setUiccApplicationsEnabled(boolean uiccApplicationsEnabled) { + mAreUiccApplicationsEnabled = uiccApplicationsEnabled; + return this; + } + + /** + * Set the port index of the Uicc card. + * + * @param portIndex The port index of the Uicc card. + * @return The builder. + */ + @NonNull + public Builder setPortIndex(int portIndex) { + mPortIndex = portIndex; + return this; + } + + /** + * Set subscription's preferred usage setting. + * + * @param usageSetting Subscription's preferred usage setting. + * @return The builder. + */ + @NonNull + public Builder setUsageSetting(@UsageSetting int usageSetting) { + mUsageSetting = usageSetting; + return this; + } + + /** + * Build the {@link SubscriptionInfo}. + * + * @return The {@link SubscriptionInfo} instance. + */ + public SubscriptionInfo build() { + return new SubscriptionInfo(this); + } + } } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 4fb65874044f..5a2b4fc3df36 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -3099,7 +3099,7 @@ public class SubscriptionManager { @SystemApi public boolean canManageSubscription(@NonNull SubscriptionInfo info, @NonNull String packageName) { - if (info == null || info.getAllAccessRules() == null || packageName == null) { + if (info == null || info.getAccessRules() == null || packageName == null) { return false; } PackageManager packageManager = mContext.getPackageManager(); @@ -3111,7 +3111,7 @@ public class SubscriptionManager { logd("Unknown package: " + packageName); return false; } - for (UiccAccessRule rule : info.getAllAccessRules()) { + for (UiccAccessRule rule : info.getAccessRules()) { if (rule.getCarrierPrivilegeStatus(packageInfo) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { return true; @@ -3456,10 +3456,20 @@ public class SubscriptionManager { * Get subscriptionInfo list of subscriptions that are in the same group of given subId. * See {@link #createSubscriptionGroup(List)} for more details. * - * Caller will either have {@link android.Manifest.permission#READ_PHONE_STATE} - * permission or had carrier privilege permission on the subscription. + * Caller must have {@link android.Manifest.permission#READ_PHONE_STATE} + * or carrier privilege permission on the subscription. * {@link TelephonyManager#hasCarrierPrivileges()} * + * <p>Starting with API level 33, the caller also needs permission to access device identifiers + * to get the list of subscriptions associated with a group UUID. + * This method can be invoked if one of the following requirements is met: + * <ul> + * <li>If the app has carrier privilege permission. + * {@link TelephonyManager#hasCarrierPrivileges()} + * <li>If the app has {@link android.Manifest.permission#READ_PHONE_STATE} permission and + * access to device identifiers. + * </ul> + * * @throws IllegalStateException if Telephony service is in bad state. * @throws SecurityException if the caller doesn't meet the requirements * outlined above. diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index b4244dd09bbd..12b4114930ca 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -191,6 +191,9 @@ public class TelephonyManager { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) private static final long CALLBACK_ON_MORE_ERROR_CODE_CHANGE = 130595455L; + // Null IMEI anomaly uuid + private static final UUID IMEI_ANOMALY_UUID = UUID.fromString( + "83905f14-6455-450c-be29-8206f0427fe9"); /** * The key to use when placing the result of {@link #requestModemActivityInfo(ResultReceiver)} * into the ResultReceiver Bundle. @@ -2181,7 +2184,11 @@ public class TelephonyManager { @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM) public String getImei() { - return getImei(getSlotIndex()); + String imei = getImei(getSlotIndex()); + if (imei == null) { + AnomalyReporter.reportAnomaly(IMEI_ANOMALY_UUID, "getImei: IMEI is null."); + } + return imei; } /** @@ -2224,7 +2231,10 @@ public class TelephonyManager { @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM) public String getImei(int slotIndex) { ITelephony telephony = getITelephony(); - if (telephony == null) return null; + if (telephony == null) { + AnomalyReporter.reportAnomaly(IMEI_ANOMALY_UUID, "getImei: telephony is null"); + return null; + } try { return telephony.getImeiForSlot(slotIndex, getOpPackageName(), getAttributionTag()); diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 54662ef2d0b5..725963bc7ca3 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -214,5 +214,34 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <service + android:name=".AssistantInteractionSessionService" + android:exported="true" + android:permission="android.permission.BIND_VOICE_INTERACTION" /> + <service + android:name=".AssistantRecognitionService" + android:exported="true" + android:label="Test Voice Interaction Service"> + <intent-filter> + <action android:name="android.speech.RecognitionService" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + <meta-data + android:name="android.speech" + android:resource="@xml/recognition_service" /> + </service> + <service + android:name=".AssistantInteractionService" + android:exported="true" + android:label="Test Voice Interaction Service" + android:permission="android.permission.BIND_VOICE_INTERACTION"> + <intent-filter> + <action android:name="android.service.voice.VoiceInteractionService" /> + </intent-filter> + <meta-data + android:name="android.voice_interaction" + android:resource="@xml/interaction_service" /> + </service> </application> + <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> </manifest> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml new file mode 100644 index 000000000000..eb7f3074ebfb --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <FrameLayout + android:id="@+id/vis_frame" + android:layout_width="match_parent" + android:layout_height="300dp" + android:layout_gravity="bottom" + android:background="#37474F"/> +</FrameLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml new file mode 100644 index 000000000000..2e661fbd3122 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml @@ -0,0 +1,20 @@ +<!-- + ~ 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. + --> + +<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android" + android:recognitionService="com.android.server.wm.flicker.testapp.AssistantRecognitionService" + android:sessionService="com.android.server.wm.flicker.testapp.AssistantInteractionSessionService" + android:supportsAssist="true" /> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml new file mode 100644 index 000000000000..2e124982084f --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml @@ -0,0 +1,17 @@ +<!-- + ~ 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. + --> + +<recognition-service xmlns:android="http://schemas.android.com/apk/res/android" /> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java new file mode 100644 index 000000000000..d60143cdf40a --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java @@ -0,0 +1,22 @@ +/* + * 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.wm.flicker.testapp; + +import android.service.voice.VoiceInteractionService; + +public class AssistantInteractionService extends VoiceInteractionService { +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java new file mode 100644 index 000000000000..d2c9b37704b8 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java @@ -0,0 +1,53 @@ +/* + * 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.wm.flicker.testapp; + +import android.content.Context; +import android.graphics.Color; +import android.os.Bundle; +import android.service.voice.VoiceInteractionSession; +import android.view.View; +import android.view.Window; + +public class AssistantInteractionSession extends VoiceInteractionSession { + + public AssistantInteractionSession(Context context) { + super(context); + } + + @Override + public void onCreate() { + Window window = getWindow().getWindow(); + if (window != null) { + window.getDecorView().setBackgroundColor(Color.TRANSPARENT); + + } + View rootView = getLayoutInflater().inflate(R.layout.assistant_session, null); + setContentView(rootView); + setUiEnabled(false); + } + + @Override + public void onShow(Bundle args, int showFlags) { + setUiEnabled(true); + } + + @Override + public void onHide() { + setUiEnabled(false); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java new file mode 100644 index 000000000000..4d6125c9a5d8 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java @@ -0,0 +1,28 @@ +/* + * 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.wm.flicker.testapp; + +import android.os.Bundle; +import android.service.voice.VoiceInteractionSession; +import android.service.voice.VoiceInteractionSessionService; + +public class AssistantInteractionSessionService extends VoiceInteractionSessionService { + @Override + public VoiceInteractionSession onNewSession(Bundle args) { + return new AssistantInteractionSession(this); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java new file mode 100644 index 000000000000..68aae4520fe9 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java @@ -0,0 +1,37 @@ +/* + * 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.wm.flicker.testapp; + +import android.content.Intent; +import android.speech.RecognitionService; + +public class AssistantRecognitionService extends RecognitionService { + @Override + protected void onStartListening(Intent recognizerIntent, Callback listener) { + + } + + @Override + protected void onCancel(Callback listener) { + + } + + @Override + protected void onStopListening(Callback listener) { + + } +} diff --git a/tools/fonts/Android.bp b/tools/fonts/Android.bp index eeb9e3ceda1e..f8629f9bd0b8 100644 --- a/tools/fonts/Android.bp +++ b/tools/fonts/Android.bp @@ -24,12 +24,7 @@ package { python_defaults { name: "fonts_python_defaults", version: { - py2: { - enabled: false, - embedded_launcher: false, - }, py3: { - enabled: true, embedded_launcher: true, }, }, |