diff options
214 files changed, 4187 insertions, 1289 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 20471682dd2e..eed248bcd2b9 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -310,6 +310,8 @@ java_aconfig_library { aconfig_declarations { name: "android.os.flags-aconfig", package: "android.os", + exportable: true, + container: "system", srcs: ["core/java/android/os/*.aconfig"], } @@ -326,6 +328,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "android.os.flags-aconfig-java-export", + aconfig_declarations: "android.os.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], + mode: "exported", +} + // VirtualDeviceManager cc_aconfig_library { name: "android.companion.virtualdevice.flags-aconfig-cc", diff --git a/apex/jobscheduler/service/aconfig/alarm.aconfig b/apex/jobscheduler/service/aconfig/alarm.aconfig index 3b9b4e70b310..bb0f3cbd5257 100644 --- a/apex/jobscheduler/service/aconfig/alarm.aconfig +++ b/apex/jobscheduler/service/aconfig/alarm.aconfig @@ -9,3 +9,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "start_user_before_scheduled_alarms" + namespace: "multiuser" + description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due" + bug: "314907186" +} diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 1f5b83ca8ae8..c1add03fa31a 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -458,13 +458,21 @@ java_defaults { libs: ["stub-annotations"], } +java_defaults { + name: "android-non-updatable_everything_from_text_defaults", + defaults: [ + "android-non-updatable_from_text_defaults", + ], + stubs_type: "everything", +} + java_api_library { name: "android-non-updatable.stubs.from-text", api_surface: "public", api_contributions: [ "api-stubs-docs-non-updatable.api.contribution", ], - defaults: ["android-non-updatable_from_text_defaults"], + defaults: ["android-non-updatable_everything_from_text_defaults"], full_api_surface_stub: "android_stubs_current.from-text", } @@ -475,7 +483,7 @@ java_api_library { "api-stubs-docs-non-updatable.api.contribution", "system-api-stubs-docs-non-updatable.api.contribution", ], - defaults: ["android-non-updatable_from_text_defaults"], + defaults: ["android-non-updatable_everything_from_text_defaults"], full_api_surface_stub: "android_system_stubs_current.from-text", } @@ -487,7 +495,7 @@ java_api_library { "system-api-stubs-docs-non-updatable.api.contribution", "test-api-stubs-docs-non-updatable.api.contribution", ], - defaults: ["android-non-updatable_from_text_defaults"], + defaults: ["android-non-updatable_everything_from_text_defaults"], full_api_surface_stub: "android_test_stubs_current.from-text", } @@ -499,7 +507,7 @@ java_api_library { "system-api-stubs-docs-non-updatable.api.contribution", "module-lib-api-stubs-docs-non-updatable.api.contribution", ], - defaults: ["android-non-updatable_from_text_defaults"], + defaults: ["android-non-updatable_everything_from_text_defaults"], full_api_surface_stub: "android_module_lib_stubs_current_full.from-text", } @@ -515,7 +523,7 @@ java_api_library { "test-api-stubs-docs-non-updatable.api.contribution", "module-lib-api-stubs-docs-non-updatable.api.contribution", ], - defaults: ["android-non-updatable_from_text_defaults"], + defaults: ["android-non-updatable_everything_from_text_defaults"], full_api_surface_stub: "android_test_module_lib_stubs_current.from-text", // This module is only used for hiddenapi, and other modules should not @@ -836,6 +844,7 @@ java_api_library { ], visibility: ["//visibility:public"], enable_validation: false, + stubs_type: "everything", } java_api_library { @@ -852,6 +861,7 @@ java_api_library { ], visibility: ["//visibility:public"], enable_validation: false, + stubs_type: "everything", } java_api_library { @@ -870,6 +880,7 @@ java_api_library { ], visibility: ["//visibility:public"], enable_validation: false, + stubs_type: "everything", } java_api_library { @@ -888,6 +899,7 @@ java_api_library { "system-api-stubs-docs-non-updatable.api.contribution", ], enable_validation: false, + stubs_type: "everything", } java_api_library { @@ -908,6 +920,7 @@ java_api_library { ], visibility: ["//visibility:public"], enable_validation: false, + stubs_type: "everything", } java_api_library { @@ -922,6 +935,7 @@ java_api_library { ], visibility: ["//visibility:public"], enable_validation: false, + stubs_type: "everything", } java_api_library { @@ -947,6 +961,7 @@ java_api_library { "//visibility:private", ], enable_validation: false, + stubs_type: "everything", } java_api_library { @@ -964,6 +979,7 @@ java_api_library { ], visibility: ["//visibility:public"], enable_validation: false, + stubs_type: "everything", } //////////////////////////////////////////////////////////////////////// diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 1718452548b3..1f1a94ef31ea 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -15321,7 +15321,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst(); - method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @Nullable @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID}) public android.telephony.CellIdentity getLastKnownCellIdentity(); + method @FlaggedApi("com.android.server.telecom.flags.get_last_known_cell_identity") @Nullable @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID}) public android.telephony.CellIdentity getLastKnownCellIdentity(); method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping(); method public int getMaxNumberOfSimultaneouslyActiveSims(); method public static long getMaxNumberVerificationTimeoutMillis(); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 083705bca09e..b25ebf69d14c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -14071,7 +14071,7 @@ public class DevicePolicyManager { public void setAuditLogEnabled(boolean enabled) { throwIfParentInstance("setAuditLogEnabled"); try { - mService.setAuditLogEnabled(mContext.getPackageName(), true); + mService.setAuditLogEnabled(mContext.getPackageName(), enabled); } catch (RemoteException re) { re.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 10954aba955c..e1a69139d88d 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -149,3 +149,10 @@ flag { description: "Allow to query whether MTE is enabled or not to check for compliance for enterprise policy" bug: "322777918" } + +flag { + name: "esim_management_ux_enabled" + namespace: "enterprise" + description: "Enable UX changes for esim management" + bug: "295301164" +} diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java index 48ea846e8d50..631772556879 100644 --- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java +++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java @@ -40,6 +40,7 @@ import java.util.Objects; public class ActivityConfigurationChangeItem extends ActivityTransactionItem { private Configuration mConfiguration; + private ActivityWindowInfo mActivityWindowInfo; @Override public void preExecute(@NonNull ClientTransactionHandler client) { @@ -55,8 +56,7 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { // TODO(lifecycler): detect if PIP or multi-window mode changed and report it here. Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged"); client.handleActivityConfigurationChanged(r, mConfiguration, INVALID_DISPLAY, - // TODO(b/287582673): add ActivityWindowInfo - new ActivityWindowInfo()); + mActivityWindowInfo); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -73,7 +73,7 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { /** Obtain an instance initialized with provided params. */ @NonNull public static ActivityConfigurationChangeItem obtain(@NonNull IBinder activityToken, - @NonNull Configuration config) { + @NonNull Configuration config, @NonNull ActivityWindowInfo activityWindowInfo) { ActivityConfigurationChangeItem instance = ObjectPool.obtain(ActivityConfigurationChangeItem.class); if (instance == null) { @@ -81,6 +81,7 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { } instance.setActivityToken(activityToken); instance.mConfiguration = new Configuration(config); + instance.mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo); return instance; } @@ -89,6 +90,7 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { public void recycle() { super.recycle(); mConfiguration = null; + mActivityWindowInfo = null; ObjectPool.recycle(this); } @@ -100,12 +102,14 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeTypedObject(mConfiguration, flags); + dest.writeTypedObject(mActivityWindowInfo, flags); } /** Read from Parcel. */ private ActivityConfigurationChangeItem(@NonNull Parcel in) { super(in); mConfiguration = in.readTypedObject(Configuration.CREATOR); + mActivityWindowInfo = in.readTypedObject(ActivityWindowInfo.CREATOR); } public static final @NonNull Creator<ActivityConfigurationChangeItem> CREATOR = @@ -128,7 +132,8 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { return false; } final ActivityConfigurationChangeItem other = (ActivityConfigurationChangeItem) o; - return Objects.equals(mConfiguration, other.mConfiguration); + return Objects.equals(mConfiguration, other.mConfiguration) + && Objects.equals(mActivityWindowInfo, other.mActivityWindowInfo); } @Override @@ -136,12 +141,14 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { int result = 17; result = 31 * result + super.hashCode(); result = 31 * result + Objects.hashCode(mConfiguration); + result = 31 * result + Objects.hashCode(mActivityWindowInfo); return result; } @Override public String toString() { return "ActivityConfigurationChange{" + super.toString() - + ",config=" + mConfiguration + "}"; + + ",config=" + mConfiguration + + ",activityWindowInfo=" + mActivityWindowInfo + "}"; } } diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index ecffe9e5a8b2..faa2c7018d6f 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -392,8 +392,6 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen return; } - Log.i(TAG, walFile.getAbsolutePath() + " " + size + " bytes: Bigger than " - + threshold + "; truncating"); try { executeForString("PRAGMA wal_checkpoint(TRUNCATE)", null, null); mConfiguration.shouldTruncateWalFile = false; diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java index 5e523c0112b1..78c8954cfe5f 100644 --- a/core/java/android/database/sqlite/SQLiteOpenHelper.java +++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java @@ -377,8 +377,7 @@ public abstract class SQLiteOpenHelper implements AutoCloseable { if (writable) { throw ex; } - Log.e(TAG, "Couldn't open " + mName - + " for writing (will try read-only):", ex); + Log.e(TAG, "Couldn't open database for writing (will try read-only):", ex); params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build(); db = SQLiteDatabase.openDatabase(filePath, params); } @@ -425,11 +424,6 @@ public abstract class SQLiteOpenHelper implements AutoCloseable { } onOpen(db); - - if (db.isReadOnly()) { - Log.w(TAG, "Opened " + mName + " in read-only mode"); - } - mDatabase = db; return db; } finally { diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index 95526a8affbb..8aacd5e3908f 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -1379,7 +1379,6 @@ public final class OutputConfiguration implements Parcelable { mSurfaceType != other.mSurfaceType || mIsDeferredConfig != other.mIsDeferredConfig || mIsShared != other.mIsShared || - mConfiguredFormat != other.mConfiguredFormat || mConfiguredDataspace != other.mConfiguredDataspace || mConfiguredGenerationId != other.mConfiguredGenerationId || !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) || diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig index 63ae28f6ff9d..2a118350610f 100644 --- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig +++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig @@ -20,3 +20,10 @@ flag { description: "Enable reporting USB data compliance warnings from HAL when set" bug: "296119135" } + +flag { + name: "enable_usb_data_signal_staking" + namespace: "preload_safety" + description: "Enables signal API with staking" + bug: "296119135" +}
\ No newline at end of file diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl index 65e498e14475..8b1577cb4b1c 100644 --- a/core/java/android/os/IVibratorManagerService.aidl +++ b/core/java/android/os/IVibratorManagerService.aidl @@ -41,5 +41,5 @@ interface IVibratorManagerService { // There is no order guarantee with respect to the two-way APIs above like // vibrate/isVibrating/cancel. oneway void performHapticFeedback(int uid, int deviceId, String opPkg, int constant, - boolean always, String reason); + boolean always, String reason, boolean fromIme); } diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index 3950c25675d8..2fe115f49099 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -623,14 +623,14 @@ public final class MessageQueue { // Message is to be inserted at tail or middle of queue. Usually we don't have to // wake up the event queue unless there is a barrier at the head of the queue and // the message is the earliest asynchronous message in the queue. - // + needWake = mBlocked && p.target == null && msg.isAsynchronous(); + // For readability, we split this portion of the function into two blocks based on // whether tail tracking is enabled. This has a minor implication for the case // where tail tracking is disabled. See the comment below. if (Flags.messageQueueTailTracking()) { - needWake = mBlocked && p.target == null && msg.isAsynchronous() - && mAsyncMessageCount == 0; if (when >= mLast.when) { + needWake = needWake && mAsyncMessageCount == 0; msg.next = null; mLast.next = msg; mLast = msg; @@ -643,6 +643,9 @@ public final class MessageQueue { if (p == null || when < p.when) { break; } + if (needWake && p.isAsynchronous()) { + needWake = false; + } } if (p == null) { /* Inserting at tail of queue */ @@ -652,7 +655,6 @@ public final class MessageQueue { prev.next = msg; } } else { - needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index 04c257b92e29..2a62c24a86e1 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -206,12 +206,13 @@ public class SystemVibrator extends Vibrator { } @Override - public void performHapticFeedback(int constant, boolean always, String reason) { + public void performHapticFeedback( + int constant, boolean always, String reason, boolean fromIme) { if (mVibratorManager == null) { Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager."); return; } - mVibratorManager.performHapticFeedback(constant, always, reason); + mVibratorManager.performHapticFeedback(constant, always, reason, fromIme); } @Override diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java index 8e8392302824..c80bcac2624f 100644 --- a/core/java/android/os/SystemVibratorManager.java +++ b/core/java/android/os/SystemVibratorManager.java @@ -147,14 +147,15 @@ public class SystemVibratorManager extends VibratorManager { } @Override - public void performHapticFeedback(int constant, boolean always, String reason) { + public void performHapticFeedback(int constant, boolean always, String reason, + boolean fromIme) { if (mService == null) { Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager service."); return; } try { mService.performHapticFeedback( - mUid, mContext.getDeviceId(), mPackageName, constant, always, reason); + mUid, mContext.getDeviceId(), mPackageName, constant, always, reason, fromIme); } catch (RemoteException e) { Log.w(TAG, "Failed to perform haptic feedback.", e); } @@ -244,8 +245,9 @@ public class SystemVibratorManager extends VibratorManager { } @Override - public void performHapticFeedback(int effectId, boolean always, String reason) { - SystemVibratorManager.this.performHapticFeedback(effectId, always, reason); + public void performHapticFeedback(int effectId, boolean always, String reason, + boolean fromIme) { + SystemVibratorManager.this.performHapticFeedback(effectId, always, reason, fromIme); } @Override diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index 46705a31f395..9df5b850188f 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -289,6 +289,15 @@ public final class VibrationAttributes implements Parcelable { } /** + * Return the original {@link AudioAttributes} used to create the vibration attributes. + * @hide + */ + @AudioAttributes.AttributeUsage + public int getOriginalAudioUsage() { + return mOriginalAudioUsage; + } + + /** * Return the flags. * @return a combined mask of all flags */ @@ -405,8 +414,8 @@ public final class VibrationAttributes implements Parcelable { return "VibrationAttributes{" + "mUsage=" + usageToString() + ", mAudioUsage= " + AudioAttributes.usageToString(mOriginalAudioUsage) - + ", mFlags=" + mFlags + ", mCategory=" + categoryToString() + + ", mFlags=" + mFlags + '}'; } diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index 2fc24142acf2..4b2d4eb833ff 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -534,10 +534,12 @@ public abstract class Vibrator { * {@code false} if the vibration for the haptic feedback should respect the applicable * vibration intensity settings. * @param reason the reason for this haptic feedback. + * @param fromIme the haptic feedback is performed from an IME. * * @hide */ - public void performHapticFeedback(int constant, boolean always, String reason) { + public void performHapticFeedback(int constant, boolean always, String reason, + boolean fromIme) { Log.w(TAG, "performHapticFeedback is not supported"); } diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java index e0b6a9fd28f0..513c4bd7ec0c 100644 --- a/core/java/android/os/VibratorManager.java +++ b/core/java/android/os/VibratorManager.java @@ -146,9 +146,11 @@ public abstract class VibratorManager { * vibration intensity settings applicable to the corresponding vibration. * {@code false} otherwise. * @param reason the reason for this haptic feedback. + * @param fromIme the haptic feedback is performed from an IME. * @hide */ - public void performHapticFeedback(int constant, boolean always, String reason) { + public void performHapticFeedback(int constant, boolean always, String reason, + boolean fromIme) { Log.w(TAG, "performHapticFeedback is not supported"); } diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index abfa4e3dd8dc..d9400accb4bd 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -1,4 +1,5 @@ package: "android.os" +container: "system" flag { name: "android_os_build_vanilla_ice_cream" @@ -40,6 +41,7 @@ flag { namespace: "profile_experiences" description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion." bug: "299069460" + is_exported: true } flag { diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java index bcdb98282679..a14a2c7e54ca 100644 --- a/core/java/android/os/vibrator/VibrationConfig.java +++ b/core/java/android/os/vibrator/VibrationConfig.java @@ -224,17 +224,19 @@ public class VibrationConfig { @Override public String toString() { return "VibrationConfig{" - + "mHapticChannelMaxVibrationAmplitude=" + mHapticChannelMaxVibrationAmplitude + + "mIgnoreVibrationsOnWirelessCharger=" + mIgnoreVibrationsOnWirelessCharger + + ", mHapticChannelMaxVibrationAmplitude=" + mHapticChannelMaxVibrationAmplitude + ", mRampStepDurationMs=" + mRampStepDurationMs + ", mRampDownDurationMs=" + mRampDownDurationMs + + ", mRequestVibrationParamsForUsages=" + + Arrays.toString(getRequestVibrationParamsForUsagesNames()) + + ", mRequestVibrationParamsTimeoutMs=" + mRequestVibrationParamsTimeoutMs + ", mDefaultAlarmIntensity=" + mDefaultAlarmVibrationIntensity + ", mDefaultHapticFeedbackIntensity=" + mDefaultHapticFeedbackIntensity + ", mDefaultMediaIntensity=" + mDefaultMediaVibrationIntensity + ", mDefaultNotificationIntensity=" + mDefaultNotificationVibrationIntensity + ", mDefaultRingIntensity=" + mDefaultRingVibrationIntensity - + ", mRequestVibrationParamsTimeoutMs=" + mRequestVibrationParamsTimeoutMs - + ", mRequestVibrationParamsForUsages=" + Arrays.toString( - getRequestVibrationParamsForUsagesNames()) + + ", mDefaultKeyboardVibrationEnabled=" + mDefaultKeyboardVibrationEnabled + "}"; } @@ -246,9 +248,13 @@ public class VibrationConfig { public void dumpWithoutDefaultSettings(IndentingPrintWriter pw) { pw.println("VibrationConfig:"); pw.increaseIndent(); + pw.println("ignoreVibrationsOnWirelessCharger = " + mIgnoreVibrationsOnWirelessCharger); pw.println("hapticChannelMaxAmplitude = " + mHapticChannelMaxVibrationAmplitude); pw.println("rampStepDurationMs = " + mRampStepDurationMs); pw.println("rampDownDurationMs = " + mRampDownDurationMs); + pw.println("requestVibrationParamsForUsages = " + + Arrays.toString(getRequestVibrationParamsForUsagesNames())); + pw.println("requestVibrationParamsTimeoutMs = " + mRequestVibrationParamsTimeoutMs); pw.decreaseIndent(); } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 8495f3747573..fa9f03da6372 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -247,8 +247,6 @@ public final class PermissionManager { private final LegacyPermissionManager mLegacyPermissionManager; - private final VirtualDeviceManager mVirtualDeviceManager; - private final ArrayMap<PackageManager.OnPermissionsChangedListener, IOnPermissionsChangeListener> mPermissionListeners = new ArrayMap<>(); private PermissionUsageHelper mUsageHelper; @@ -269,7 +267,6 @@ public final class PermissionManager { mPermissionManager = IPermissionManager.Stub.asInterface(ServiceManager.getServiceOrThrow( "permissionmgr")); mLegacyPermissionManager = context.getSystemService(LegacyPermissionManager.class); - mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class); } /** @@ -1918,14 +1915,18 @@ public final class PermissionManager { if (deviceId == Context.DEVICE_ID_DEFAULT) { persistentDeviceId = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; } else if (android.companion.virtual.flags.Flags.vdmPublicApis()) { - VirtualDevice virtualDevice = mVirtualDeviceManager.getVirtualDevice(deviceId); - if (virtualDevice == null) { - Slog.e(LOG_TAG, "Virtual device is not found with device Id " + deviceId); - return null; - } - persistentDeviceId = virtualDevice.getPersistentDeviceId(); - if (persistentDeviceId == null) { - Slog.e(LOG_TAG, "Cannot find persistent device Id for " + deviceId); + VirtualDeviceManager virtualDeviceManager = mContext.getSystemService( + VirtualDeviceManager.class); + if (virtualDeviceManager != null) { + VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId); + if (virtualDevice == null) { + Slog.e(LOG_TAG, "Virtual device is not found with device Id " + deviceId); + return null; + } + persistentDeviceId = virtualDevice.getPersistentDeviceId(); + if (persistentDeviceId == null) { + Slog.e(LOG_TAG, "Cannot find persistent device Id for " + deviceId); + } } } else { Slog.e(LOG_TAG, "vdmPublicApis flag is not enabled when device Id " + deviceId diff --git a/core/java/android/security/ConfirmationPrompt.java b/core/java/android/security/ConfirmationPrompt.java index d8c44adcc322..f626149b03c4 100644 --- a/core/java/android/security/ConfirmationPrompt.java +++ b/core/java/android/security/ConfirmationPrompt.java @@ -92,7 +92,6 @@ public class ConfirmationPrompt { private Executor mExecutor; private Context mContext; - private final KeyStore mKeyStore = KeyStore.getInstance(); private AndroidProtectedConfirmation mProtectedConfirmation; private AndroidProtectedConfirmation getService() { diff --git a/core/java/android/security/keystore/recovery/RecoveryController.java b/core/java/android/security/keystore/recovery/RecoveryController.java index f1054ec8ef15..c171c1b4b3b6 100644 --- a/core/java/android/security/keystore/recovery/RecoveryController.java +++ b/core/java/android/security/keystore/recovery/RecoveryController.java @@ -26,7 +26,6 @@ import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; -import android.security.KeyStore; import android.security.KeyStore2; import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore2.AndroidKeyStoreProvider; @@ -272,11 +271,9 @@ public class RecoveryController { public static final int ERROR_KEY_NOT_FOUND = 30; private final ILockSettings mBinder; - private final KeyStore mKeyStore; - private RecoveryController(ILockSettings binder, KeyStore keystore) { + private RecoveryController(ILockSettings binder) { mBinder = binder; - mKeyStore = keystore; } /** @@ -296,7 +293,7 @@ public class RecoveryController { // lockSettings may be null. ILockSettings lockSettings = ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings")); - return new RecoveryController(lockSettings, KeyStore.getInstance()); + return new RecoveryController(lockSettings); } /** diff --git a/core/java/android/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig index b1bca96c5c09..cedba85f55dc 100644 --- a/core/java/android/tracing/flags.aconfig +++ b/core/java/android/tracing/flags.aconfig @@ -8,7 +8,7 @@ flag { } flag { - name: "perfetto_protolog" + name: "perfetto_protolog_tracing" namespace: "windowing_tools" description: "Migrate protolog to Perfetto" bug: "276432490" diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index 00657ea35e02..66655fca8fc3 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -188,8 +188,7 @@ public class HandwritingInitiator { // check whether the stylus we are tracking goes up. if (mState != null) { mState.mShouldInitHandwriting = false; - if (!mState.mHasInitiatedHandwriting - && !mState.mHasPreparedHandwritingDelegation) { + if (!mState.mHandled) { // The user just did a click, long click or another stylus gesture, // show hover icon again for the connected view. mShowHoverIconForConnectedView = true; @@ -204,16 +203,14 @@ public class HandwritingInitiator { // Either we've already tried to initiate handwriting, or the ongoing MotionEvent // sequence is considered to be tap, long-click or other gestures. if (!mState.mShouldInitHandwriting || mState.mExceedHandwritingSlop) { - return mState.mHasInitiatedHandwriting - || mState.mHasPreparedHandwritingDelegation; + return mState.mHandled; } final long timeElapsed = motionEvent.getEventTime() - mState.mStylusDownTimeInMillis; if (timeElapsed > mHandwritingTimeoutInMillis) { mState.mShouldInitHandwriting = false; - return mState.mHasInitiatedHandwriting - || mState.mHasPreparedHandwritingDelegation; + return mState.mHandled; } final int pointerIndex = motionEvent.findPointerIndex(mState.mStylusPointerId); @@ -223,7 +220,7 @@ public class HandwritingInitiator { mState.mExceedHandwritingSlop = true; View candidateView = findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY, /* isHover */ false); - if (candidateView != null) { + if (candidateView != null && candidateView.isEnabled()) { if (candidateView == getConnectedOrFocusedView()) { if (!mInitiateWithoutConnection && !candidateView.hasFocus()) { requestFocusWithoutReveal(candidateView); @@ -246,7 +243,7 @@ public class HandwritingInitiator { } } } - return mState.mHasInitiatedHandwriting || mState.mHasPreparedHandwritingDelegation; + return mState.mHandled; } return false; } @@ -382,7 +379,7 @@ public class HandwritingInitiator { @VisibleForTesting public void startHandwriting(@NonNull View view) { mImm.startStylusHandwriting(view); - mState.mHasInitiatedHandwriting = true; + mState.mHandled = true; mState.mShouldInitHandwriting = false; mShowHoverIconForConnectedView = false; if (view instanceof TextView) { @@ -402,13 +399,12 @@ public class HandwritingInitiator { mImm.startConnectionlessStylusHandwritingForDelegation( view, getCursorAnchorInfoForConnectionless(view), delegatePackageName, view::post, new DelegationCallback(view, delegatePackageName)); - mState.mHasInitiatedHandwriting = true; mState.mShouldInitHandwriting = false; } else { mImm.prepareStylusHandwritingDelegation(view, delegatePackageName); view.getHandwritingDelegatorCallback().run(); - mState.mHasPreparedHandwritingDelegation = true; } + mState.mHandled = true; } /** @@ -455,7 +451,7 @@ public class HandwritingInitiator { private void onDelegationAccepted(View view) { if (mState != null) { - mState.mHasInitiatedHandwriting = true; + mState.mHandled = true; mState.mShouldInitHandwriting = false; } if (view instanceof TextView) { @@ -795,12 +791,12 @@ public class HandwritingInitiator { * This boolean will be set to false, and it won't request to start handwriting. */ private boolean mShouldInitHandwriting; + /** - * Whether handwriting mode has already been initiated for the current MotionEvent sequence. + * Whether the current MotionEvent sequence has been handled by the handwriting initiator, + * either by initiating handwriting mode, or by preparing handwriting delegation. */ - private boolean mHasInitiatedHandwriting; - - private boolean mHasPreparedHandwritingDelegation; + private boolean mHandled; /** * Whether the current ongoing stylus MotionEvent sequence already exceeds the @@ -838,8 +834,7 @@ public class HandwritingInitiator { mStylusDownY = motionEvent.getY(actionIndex); mShouldInitHandwriting = true; - mHasInitiatedHandwriting = false; - mHasPreparedHandwritingDelegation = false; + mHandled = false; mExceedHandwritingSlop = false; } } @@ -1052,8 +1047,6 @@ public class HandwritingInitiator { // Fall back to the old delegation flow mImm.prepareStylusHandwritingDelegation(mView, mDelegatePackageName); mView.getHandwritingDelegatorCallback().run(); - mState.mHasInitiatedHandwriting = false; - mState.mHasPreparedHandwritingDelegation = true; break; } } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index d68a47c54d4b..e126836020b4 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -148,13 +148,13 @@ interface IWindowSession { int seqId); @UnsupportedAppUsage - boolean performHapticFeedback(int effectId, boolean always); + boolean performHapticFeedback(int effectId, boolean always, boolean fromIme); /** * Called by attached views to perform predefined haptic feedback without requiring VIBRATE * permission. */ - oneway void performHapticFeedbackAsync(int effectId, boolean always); + oneway void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme); /** * Initiate the drag operation itself diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java index f5f4fd9f7042..9099f9855eab 100644 --- a/core/java/android/view/PointerIcon.java +++ b/core/java/android/view/PointerIcon.java @@ -428,13 +428,11 @@ public final class PointerIcon implements Parcelable { private BitmapDrawable getBitmapDrawableFromVectorDrawable(Resources resources, VectorDrawable vectorDrawable) { - Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), - vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - // BitmapDrawables and Bitmap have a default density of DisplayMetrics.DENSITY_DEVICE, - // (which is deprecated in favor of DENSITY_DEVICE_STABLE/resources.densityDpi). In - // rare cases when device density differs from the resource density, the bitmap will - // scale as the BitmapDrawable is created. Avoid by explicitly setting density here. - bitmap.setDensity(resources.getDisplayMetrics().densityDpi); + // Ensure we pass the display metrics into the Bitmap constructor so that it is initialized + // with the correct density. + Bitmap bitmap = Bitmap.createBitmap(resources.getDisplayMetrics(), + vectorDrawable.getIntrinsicWidth(), + vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888, true /* hasAlpha */); Canvas canvas = new Canvas(bitmap); vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); vectorDrawable.draw(canvas); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 042af1f0fb15..3478286abd57 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -25,6 +25,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW; @@ -16815,10 +16816,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mViewRootImpl.getWindowVisibleDisplayFrame(outRect); return; } - // The view is not attached to a display so we don't have a context. - // Make a best guess about the display size. - Display d = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); - d.getRectSize(outRect); + // TODO (b/327559224): Refine the behavior to better reflect the window environment with API + // doc updates. + final WindowManager windowManager = mContext.getSystemService(WindowManager.class); + final WindowMetrics metrics = windowManager.getMaximumWindowMetrics(); + final Insets insets = metrics.getWindowInsets().getInsets( + WindowInsets.Type.navigationBars() | WindowInsets.Type.displayCutout()); + outRect.set(metrics.getBounds()); + outRect.inset(insets); + outRect.offsetTo(0, 0); } /** @@ -28371,15 +28377,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } final boolean always = (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0; + boolean fromIme = false; + if (mAttachInfo.mViewRootImpl != null) { + fromIme = mAttachInfo.mViewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD; + } if (Flags.useVibratorHapticFeedback()) { if (!mAttachInfo.canPerformHapticFeedback()) { return false; } getSystemVibrator().performHapticFeedback( - feedbackConstant, always, "View#performHapticFeedback"); + feedbackConstant, always, "View#performHapticFeedback", fromIme); return true; } - return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, always); + return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, always, fromIme); } private Vibrator getSystemVibrator() { @@ -31422,7 +31432,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, interface Callbacks { void playSoundEffect(int effectId); - boolean performHapticFeedback(int effectId, boolean always); + boolean performHapticFeedback(int effectId, boolean always, boolean fromIme); } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 333cbb39d9c7..708751a25053 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -9201,18 +9201,18 @@ public final class ViewRootImpl implements ViewParent, * {@inheritDoc} */ @Override - public boolean performHapticFeedback(int effectId, boolean always) { + public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) { if ((mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) { return false; } try { if (USE_ASYNC_PERFORM_HAPTIC_FEEDBACK) { - mWindowSession.performHapticFeedbackAsync(effectId, always); + mWindowSession.performHapticFeedbackAsync(effectId, always, fromIme); return true; } else { // Original blocking binder call path. - return mWindowSession.performHapticFeedback(effectId, always); + return mWindowSession.performHapticFeedback(effectId, always, fromIme); } } catch (RemoteException e) { return false; diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 2b2c50725749..22d8ed91d455 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -479,13 +479,13 @@ public class WindowlessWindowManager implements IWindowSession { } @Override - public boolean performHapticFeedback(int effectId, boolean always) { + public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) { return false; } @Override - public void performHapticFeedbackAsync(int effectId, boolean always) { - performHapticFeedback(effectId, always); + public void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme) { + performHapticFeedback(effectId, always, fromIme); } @Override diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index fcc8344cbcd9..68940d699076 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1153,6 +1153,7 @@ public final class InputMethodManager { } final boolean startInput; synchronized (mH) { + mImeDispatcher.clear(); if (getBindSequenceLocked() != sequence) { return; } diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig index 51890ecbecb5..94c72c60ecd0 100644 --- a/core/java/android/window/flags/responsible_apis.aconfig +++ b/core/java/android/window/flags/responsible_apis.aconfig @@ -46,5 +46,5 @@ flag { name: "bal_respect_app_switch_state_when_check_bound_by_foreground_uid" namespace: "responsible_apis" description: "Prevent BAL based on it is bound by foreground Uid but the app switch is stopped." - bug: "171459802" + bug: "283801068" } diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java index d9ac5a9fe47a..30de5468001a 100644 --- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java @@ -61,7 +61,7 @@ public class LegacyProtoLogImpl implements IProtoLog { private static final int PER_CHUNK_SIZE = 1024; private static final String TAG = "ProtoLog"; private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; - static final String PROTOLOG_VERSION = "1.0.0"; + static final String PROTOLOG_VERSION = "2.0.0"; private static final int DEFAULT_PER_CHUNK_SIZE = 0; private final File mLogFile; diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java index 78bed9478d84..896538564c2f 100644 --- a/core/java/com/android/internal/protolog/ProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java @@ -98,7 +98,7 @@ public class ProtoLogImpl { */ public static synchronized IProtoLog getSingleInstance() { if (sServiceInstance == null) { - if (android.tracing.Flags.perfettoProtolog()) { + if (android.tracing.Flags.perfettoProtologTracing()) { sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath); } else { diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp index 06d5eb305ff0..d5f17da0a072 100644 --- a/core/jni/LayoutlibLoader.cpp +++ b/core/jni/LayoutlibLoader.cpp @@ -364,7 +364,7 @@ using namespace android; // Called right before aborting by LOG_ALWAYS_FATAL. Print the pending exception. void abort_handler(const char* abort_message) { - ALOGE("Abort to abort the process..."); + ALOGE("About to abort the process..."); JNIEnv* env = NULL; if (javaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto index db99e5b53875..9151958e2b94 100644 --- a/core/proto/android/server/vibrator/vibratormanagerservice.proto +++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto @@ -79,11 +79,28 @@ message CombinedVibrationEffectProto { repeated int32 delays = 2; } +// Next Tag: 5 message VibrationAttributesProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional int32 usage = 1; optional int32 audio_usage = 2; optional int32 flags = 3; + optional int32 category = 4; +} + +// Next Tag: 4 +message VibrationParamProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional VibrationScaleParamProto scale = 1; + optional int64 create_time = 2; + optional bool is_from_request = 3; +} + +// Next Tag: 3 +message VibrationScaleParamProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 types_mask = 1; + optional float scale = 2; } // Next Tag: 9 @@ -132,16 +149,19 @@ message VibrationProto { } } -// Next Tag: 25 +// Next Tag: 29 message VibratorManagerServiceDumpProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; repeated int32 vibrator_ids = 1; optional VibrationProto current_vibration = 2; optional bool is_vibrating = 3; + optional int32 is_vibrator_controller_registered = 27; optional VibrationProto current_external_vibration = 4; optional bool vibrator_under_external_control = 5; optional bool low_power_mode = 6; optional bool vibrate_on = 24; + optional bool keyboard_vibration_on = 25; + optional int32 default_vibration_amplitude = 26; optional int32 alarm_intensity = 18; optional int32 alarm_default_intensity = 19; optional int32 haptic_feedback_intensity = 7; @@ -158,5 +178,6 @@ message VibratorManagerServiceDumpProto { repeated VibrationProto previous_notification_vibrations = 14; repeated VibrationProto previous_alarm_vibrations = 15; repeated VibrationProto previous_vibrations = 16; - repeated VibrationProto previous_external_vibrations = 17; + repeated VibrationParamProto previous_vibration_params = 28; + reserved 17; // prev previous_external_vibrations }
\ No newline at end of file diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 6134e788be82..967edde83cc9 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4101,6 +4101,13 @@ <!-- How close vibration request should be when they're aggregated for dumpsys, in ms. --> <integer name="config_previousVibrationsDumpAggregationTimeMillisLimit">1000</integer> + <!-- How long history of vibration control service should be kept for the dumpsys. --> + <integer name="config_vibratorControlServiceDumpSizeLimit">50</integer> + + <!-- How close requests to vibration control service should be when they're aggregated for + dumpsys, in ms. --> + <integer name="config_vibratorControlServiceDumpAggregationTimeMillisLimit">60000</integer> + <!-- The default vibration strength, must be between 1 and 255 inclusive. --> <integer name="config_defaultVibrationAmplitude">255</integer> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index c87b7cdb1e8e..5e3e1b0bbb43 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -84,7 +84,7 @@ CarrierConfigManager#KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_STRING_ARRAY. If 0, the device always switch to the higher score SIM. If < 0, the network type and signal strength based auto switch is disabled. --> - <integer name="auto_data_switch_score_tolerance">4000</integer> + <integer name="auto_data_switch_score_tolerance">-1</integer> <java-symbol type="integer" name="auto_data_switch_score_tolerance" /> <!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2f5183fc1455..ee51ed020be6 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2083,6 +2083,8 @@ <java-symbol type="integer" name="config_recentVibrationsDumpSizeLimit" /> <java-symbol type="integer" name="config_previousVibrationsDumpSizeLimit" /> <java-symbol type="integer" name="config_previousVibrationsDumpAggregationTimeMillisLimit" /> + <java-symbol type="integer" name="config_vibratorControlServiceDumpSizeLimit" /> + <java-symbol type="integer" name="config_vibratorControlServiceDumpAggregationTimeMillisLimit" /> <java-symbol type="integer" name="config_defaultVibrationAmplitude" /> <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" /> <java-symbol type="dimen" name="config_keyboardHapticFeedbackFixedAmplitude" /> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 64c17bdfa731..d115bf306b45 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -247,7 +247,7 @@ public class ActivityThreadTest { newConfig.smallestScreenWidthDp++; transaction = newTransaction(activityThread); transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain( - activity.getActivityToken(), newConfig)); + activity.getActivityToken(), newConfig, new ActivityWindowInfo())); appThread.scheduleTransaction(transaction); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -455,11 +455,11 @@ public class ActivityThreadTest { transaction = newTransaction(activityThread); transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain( - activity.getActivityToken(), activityConfigLandscape)); + activity.getActivityToken(), activityConfigLandscape, new ActivityWindowInfo())); transaction.addTransactionItem(ConfigurationChangeItem.obtain( processConfigPortrait, DEVICE_ID_INVALID)); transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain( - activity.getActivityToken(), activityConfigPortrait)); + activity.getActivityToken(), activityConfigPortrait, new ActivityWindowInfo())); appThread.scheduleTransaction(transaction); activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS); @@ -883,7 +883,7 @@ public class ActivityThreadTest { private static ClientTransaction newActivityConfigTransaction(@NonNull Activity activity, @NonNull Configuration config) { final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain( - activity.getActivityToken(), config); + activity.getActivityToken(), config, new ActivityWindowInfo()); final ClientTransaction transaction = newTransaction(activity); transaction.addTransactionItem(item); diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java index 85a1b4ee3ebd..4db5d1bf4f67 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java @@ -107,7 +107,7 @@ public class ClientTransactionItemTest { @Test public void testActivityConfigurationChangeItem_getContextToUpdate() { final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem - .obtain(mActivityToken, mConfiguration); + .obtain(mActivityToken, mConfiguration, new ActivityWindowInfo()); final Context context = item.getContextToUpdate(mHandler); assertEquals(mActivity, context); diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index 906558f7603b..31ea6759c710 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -82,7 +82,8 @@ public class ObjectPoolTests { @Test public void testRecycleActivityConfigurationChangeItem() { - testRecycle(() -> ActivityConfigurationChangeItem.obtain(mActivityToken, config())); + testRecycle(() -> ActivityConfigurationChangeItem.obtain(mActivityToken, config(), + new ActivityWindowInfo())); } @Test diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index dbb090fe795b..75347bf2c8de 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -95,8 +95,11 @@ public class TransactionParcelTests { @Test public void testActivityConfigChange() { // Write to parcel + final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo(); + activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 500, 1000), + new Rect(0, 0, 500, 500)); ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain( - mActivityToken, config()); + mActivityToken, config(), activityWindowInfo); writeAndPrepareForReading(item); // Read from parcel and assert @@ -300,7 +303,7 @@ public class TransactionParcelTests { // Write to parcel NewIntentItem callback1 = NewIntentItem.obtain(mActivityToken, new ArrayList<>(), true); ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain( - mActivityToken, config()); + mActivityToken, config(), new ActivityWindowInfo()); StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken, 78 /* configChanges */); @@ -327,7 +330,7 @@ public class TransactionParcelTests { // Write to parcel NewIntentItem callback1 = NewIntentItem.obtain(mActivityToken, new ArrayList<>(), true); ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain( - mActivityToken, config()); + mActivityToken, config(), new ActivityWindowInfo()); ClientTransaction transaction = ClientTransaction.obtain(null /* client */); transaction.addTransactionItem(callback1); diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index 7c58de67ded6..1a242eff73b1 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -451,7 +451,7 @@ public class ViewRootImplTest { ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, display); boolean result = viewRootImpl.performHapticFeedback( - HapticFeedbackConstants.CONTEXT_CLICK, true); + HapticFeedbackConstants.CONTEXT_CLICK, true, false /* fromIme */); assertThat(result).isFalse(); } diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index 51eb41c5a271..b60b806f3444 100644 --- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -472,6 +472,26 @@ public class HandwritingInitiatorTest { } @Test + public void onTouchEvent_doesNothing_viewDisabled() { + mTestView1.setEnabled(false); + + final int x1 = (sHwArea1.left + sHwArea1.right) / 2; + final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; + MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent1); + + final int x2 = x1 + mHandwritingSlop * 2; + final int y2 = y1; + + MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent2); + + // HandwritingInitiator will not request focus if it is disabled. + verify(mTestView1, never()).requestFocus(); + verify(mHandwritingInitiator, never()).startHandwriting(mTestView1); + } + + @Test public void onTouchEvent_focusView_inputConnectionAlreadyBuilt_stylusMoveOnce_withinHWArea() { if (!mInitiateWithoutConnection) { mHandwritingInitiator.onInputConnectionCreated(mTestView1); diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index e8c7a53ddcff..0231d3abd19e 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1,5 +1,5 @@ { - "version": "1.0.0", + "version": "2.0.0", "messages": { "7286191062634870297": { "message": "Binding proc %s with config %s", diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java index 62fe54f1f089..ef03d3a3b286 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java @@ -19,9 +19,9 @@ package android.security.keystore; import android.annotation.NonNull; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; -import android.security.KeyStore; import java.io.IOException; +import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; @@ -47,13 +47,13 @@ public class AndroidKeyStoreProvider extends Provider { } /** - * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto + * Gets the Android KeyStore operation handle corresponding to the provided JCA crypto * primitive. * * <p>The following primitives are supported: {@link Cipher} and {@link Mac}. * - * @return KeyStore operation handle or {@code 0} if the provided primitive's KeyStore operation - * is not in progress. + * @return Android KeyStore operation handle or {@code 0} if the provided primitive's Android + * KeyStore operation is not in progress. * * @throws IllegalArgumentException if the provided primitive is not supported or is not backed * by AndroidKeyStore provider. @@ -67,10 +67,10 @@ public class AndroidKeyStoreProvider extends Provider { } /** - * Returns an {@code AndroidKeyStore} {@link java.security.KeyStore}} of the specified UID. - * The {@code KeyStore} contains keys and certificates owned by that UID. Such cross-UID - * access is permitted to a few system UIDs and only to a few other UIDs (e.g., Wi-Fi, VPN) - * all of which are system. + * Returns an {@code AndroidKeyStore} {@link KeyStore} of the specified UID. The {@code + * KeyStore} contains keys and certificates owned by that UID. Such cross-UID access is + * permitted to a few system UIDs and only to a few other UIDs (e.g., Wi-Fi, VPN) all of which + * are system. * * <p>Note: the returned {@code KeyStore} is already initialized/loaded. Thus, there is * no need to invoke {@code load} on it. @@ -84,12 +84,12 @@ public class AndroidKeyStoreProvider extends Provider { */ @SystemApi @NonNull - public static java.security.KeyStore getKeyStoreForUid(int uid) + public static KeyStore getKeyStoreForUid(int uid) throws KeyStoreException, NoSuchProviderException { - final java.security.KeyStore.LoadStoreParameter loadParameter = + final KeyStore.LoadStoreParameter loadParameter = new android.security.keystore2.AndroidKeyStoreLoadStoreParameter( KeyProperties.legacyUidToNamespace(uid)); - java.security.KeyStore result = java.security.KeyStore.getInstance(PROVIDER_NAME); + KeyStore result = KeyStore.getInstance(PROVIDER_NAME); try { result.load(loadParameter); } catch (NoSuchAlgorithmException | CertificateException | IOException e) { diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 244fe3033dca..7aecfd8d4a0d 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -910,7 +910,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu /** * Returns whether this key is critical to the device encryption flow. * - * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION + * @see Builder#setCriticalToDeviceEncryption(boolean) * @hide */ public boolean isCriticalToDeviceEncryption() { diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index 2495d1a85864..31b4a5eac619 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -569,7 +569,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { /** * Return whether this key is critical to the device encryption flow. * - * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION + * @see Builder#setCriticalToDeviceEncryption(boolean) * @hide */ public boolean isCriticalToDeviceEncryption() { @@ -1105,9 +1105,10 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * Set whether this key is critical to the device encryption flow * * This is a special flag only available to system servers to indicate the current key - * is part of the device encryption flow. + * is part of the device encryption flow. Setting this flag causes the key to not + * be cryptographically bound to the LSKF even if the key is otherwise authentication + * bound. * - * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION * @hide */ public Builder setCriticalToDeviceEncryption(boolean critical) { diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java index 2c709ae1ac5b..c42c9e4d99a6 100644 --- a/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java +++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java @@ -16,18 +16,16 @@ package android.security.keystore; -import android.security.KeyStore; - /** - * Cryptographic operation backed by {@link KeyStore}. + * Cryptographic operation backed by Android KeyStore. * * @hide */ public interface KeyStoreCryptoOperation { /** - * Gets the KeyStore operation handle of this crypto operation. + * Gets the Android KeyStore operation handle of this crypto operation. * - * @return handle or {@code 0} if the KeyStore operation is not in progress. + * @return handle or {@code 0} if the Android KeyStore operation is not in progress. */ long getOperationHandle(); } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java index a8dd7f3f8b14..8eca67f090d4 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java @@ -16,7 +16,6 @@ package android.security.keystore2; -import android.security.KeyStore; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyInfo; @@ -39,8 +38,6 @@ import java.security.spec.X509EncodedKeySpec; */ public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi { - private final KeyStore mKeyStore = KeyStore.getInstance(); - @Override protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpecClass) throws InvalidKeySpecException { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java index d204f13d4d78..99100de12684 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java @@ -17,7 +17,6 @@ package android.security.keystore2; import android.annotation.NonNull; -import android.security.KeyStore; import android.security.KeyStore2; import android.security.KeyStoreSecurityLevel; import android.security.keymaster.KeymasterDefs; @@ -161,13 +160,13 @@ public class AndroidKeyStoreProvider extends Provider { } /** - * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto + * Gets the Android KeyStore operation handle corresponding to the provided JCA crypto * primitive. * * <p>The following primitives are supported: {@link Cipher}, {@link Signature} and {@link Mac}. * - * @return KeyStore operation handle or {@code 0} if the provided primitive's KeyStore operation - * is not in progress. + * @return Android KeyStore operation handle or {@code 0} if the provided primitive's Android + * KeyStore operation is not in progress. * * @throws IllegalArgumentException if the provided primitive is not supported or is not backed * by AndroidKeyStore provider. diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java index 2682eb657963..22230916b084 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java @@ -18,7 +18,6 @@ package android.security.keystore2; import android.annotation.NonNull; import android.security.GateKeeper; -import android.security.KeyStore; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import android.security.keystore.KeyGenParameterSpec; @@ -46,8 +45,6 @@ import javax.crypto.spec.SecretKeySpec; */ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { - private final KeyStore mKeyStore = KeyStore.getInstance(); - @Override protected KeySpec engineGetKeySpec(SecretKey key, @SuppressWarnings("rawtypes") Class keySpecClass) throws InvalidKeySpecException { diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java index 07d6a69eda01..5bd98bce9f39 100644 --- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java @@ -16,12 +16,11 @@ package android.security.keystore2; -import android.security.KeyStore; import android.security.KeyStoreException; /** - * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's - * {@code update} and {@code finish} operations. + * Helper for streaming a crypto operation's input and output via KeyStore service's {@code update} + * and {@code finish} operations. * * <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's * update and finish operations. Firstly, KeyStore's update operation can consume only a limited diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java index 08b7bb89d10c..39cfacec8447 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java @@ -201,7 +201,7 @@ class SplitContainer { return null; } return new SplitInfo(primaryActivityStack, secondaryActivityStack, - mCurrentSplitAttributes, mToken); + mCurrentSplitAttributes, SplitInfo.Token.createFromBinder(mToken)); } static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index ae3a854baf9f..038d0081ead8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -35,6 +35,7 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHA import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; +import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_ACTIVITY_STACK_TOKEN; import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG; import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; @@ -112,10 +113,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen static final boolean ENABLE_SHELL_TRANSITIONS = SystemProperties.getBoolean("persist.wm.debug.shell_transit", true); - // TODO(b/295993745): remove after prebuilt library is updated. - private static final String KEY_ACTIVITY_STACK_TOKEN = - "androidx.window.extensions.embedding.ActivityStackToken"; - @VisibleForTesting @GuardedBy("mLock") final SplitPresenter mPresenter; @@ -554,7 +551,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Override - public void updateActivityStackAttributes(@NonNull IBinder activityStackToken, + public void updateActivityStackAttributes(@NonNull ActivityStack.Token activityStackToken, @NonNull ActivityStackAttributes attributes) { if (!Flags.activityEmbeddingOverlayPresentationFlag()) { return; @@ -563,7 +560,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen Objects.requireNonNull(attributes); synchronized (mLock) { - final TaskFragmentContainer container = getContainer(activityStackToken); + final TaskFragmentContainer container = getContainer(activityStackToken.getRawToken()); if (container == null) { Log.w(TAG, "Cannot find TaskFragmentContainer for token:" + activityStackToken); return; @@ -583,13 +580,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override @Nullable - public ParentContainerInfo getParentContainerInfo(@NonNull IBinder activityStackToken) { + public ParentContainerInfo getParentContainerInfo( + @NonNull ActivityStack.Token activityStackToken) { if (!Flags.activityEmbeddingOverlayPresentationFlag()) { return null; } Objects.requireNonNull(activityStackToken); synchronized (mLock) { - final TaskFragmentContainer container = getContainer(activityStackToken); + final TaskFragmentContainer container = getContainer(activityStackToken.getRawToken()); if (container == null) { return null; } @@ -601,7 +599,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override @Nullable - public IBinder getActivityStackToken(@NonNull String tag) { + public ActivityStack.Token getActivityStackToken(@NonNull String tag) { if (!Flags.activityEmbeddingOverlayPresentationFlag()) { return null; } @@ -612,7 +610,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (taskFragmentContainer == null) { return null; } - return taskFragmentContainer.getTaskFragmentToken(); + return ActivityStack.Token.createFromBinder(taskFragmentContainer + .getTaskFragmentToken()); } } @@ -2761,8 +2760,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // TODO(b/232042367): Consolidate the activity create handling so that we can handle // cross-process the same as normal. - IBinder activityStackToken = options.getBinder(KEY_ACTIVITY_STACK_TOKEN); - if (activityStackToken != null) { + final Bundle bundle = options.getBundle(KEY_ACTIVITY_STACK_TOKEN); + if (bundle != null) { + final IBinder activityStackToken = ActivityStack.Token.readFromBundle(bundle) + .getRawToken(); // Put activityStack token to #KEY_LAUNCH_TASK_FRAGMENT_TOKEN to launch the activity // into the taskFragment associated with the token. options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, activityStackToken); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 6fe8e50f105f..a6bf99d4add5 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -367,7 +367,8 @@ class TaskFragmentContainer { if (activities == null) { return null; } - return new ActivityStack(activities, isEmpty(), mToken, mOverlayTag); + return new ActivityStack(activities, isEmpty(), + ActivityStack.Token.createFromBinder(mToken), mOverlayTag); } /** Adds the activity that will be reparented to this container. */ diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 34d43ad56bb4..28fbadbebe7f 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -399,7 +399,8 @@ public class OverlayPresentationTest { new ActivityStackAttributes.Builder().build())); assertThrows(NullPointerException.class, () -> - mSplitController.updateActivityStackAttributes(new Binder(), null)); + mSplitController.updateActivityStackAttributes( + ActivityStack.Token.createFromBinder(new Binder()), null)); verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any()); } @@ -408,7 +409,8 @@ public class OverlayPresentationTest { public void testUpdateActivityStackAttributes_nullContainer_earlyReturn() { final TaskFragmentContainer container = mSplitController.newContainer(mActivity, mActivity.getTaskId()); - mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(), + mSplitController.updateActivityStackAttributes( + ActivityStack.Token.createFromBinder(container.getTaskFragmentToken()), new ActivityStackAttributes.Builder().build()); verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any()); @@ -418,7 +420,8 @@ public class OverlayPresentationTest { public void testUpdateActivityStackAttributes_notOverlay_earlyReturn() { final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity); - mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(), + mSplitController.updateActivityStackAttributes( + ActivityStack.Token.createFromBinder(container.getTaskFragmentToken()), new ActivityStackAttributes.Builder().build()); verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any()); @@ -431,7 +434,8 @@ public class OverlayPresentationTest { final ActivityStackAttributes attrs = new ActivityStackAttributes.Builder().build(); final IBinder token = container.getTaskFragmentToken(); - mSplitController.updateActivityStackAttributes(token, attrs); + mSplitController.updateActivityStackAttributes(ActivityStack.Token.createFromBinder(token), + attrs); verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs), any()); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index b60943a60076..00f8b5925d66 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -1437,7 +1437,7 @@ public class SplitControllerTest { @Test public void testUpdateSplitAttributes_nullParams_throwException() { assertThrows(NullPointerException.class, - () -> mSplitController.updateSplitAttributes(null, SPLIT_ATTRIBUTES)); + () -> mSplitController.updateSplitAttributes((IBinder) null, SPLIT_ATTRIBUTES)); final SplitContainer splitContainer = mock(SplitContainer.class); final IBinder token = new Binder(); diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 0ecf1f8f1feb..8829d1b9e0e1 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -212,76 +212,3 @@ android_library { plugins: ["dagger2-compiler"], use_resource_processor: true, } - -android_app { - name: "WindowManagerShellRobolectric", - platform_apis: true, - static_libs: [ - "WindowManager-Shell", - ], - manifest: "multivalentTests/AndroidManifestRobolectric.xml", - use_resource_processor: true, -} - -android_robolectric_test { - name: "WMShellRobolectricTests", - instrumentation_for: "WindowManagerShellRobolectric", - upstream: true, - java_resource_dirs: [ - "multivalentTests/robolectric/config", - ], - srcs: [ - "multivalentTests/src/**/*.kt", - ], - // TODO(b/323188766): Include BubbleStackViewTest once the robolectric issue is fixed. - exclude_srcs: ["multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt"], - static_libs: [ - "junit", - "androidx.test.runner", - "androidx.test.rules", - "androidx.test.ext.junit", - "mockito-robolectric-prebuilt", - "mockito-kotlin2", - "truth", - ], -} - -android_test { - name: "WMShellMultivalentTestsOnDevice", - srcs: [ - "multivalentTests/src/**/*.kt", - ], - static_libs: [ - "WindowManager-Shell", - "junit", - "androidx.test.runner", - "androidx.test.rules", - "androidx.test.ext.junit", - "frameworks-base-testutils", - "mockito-kotlin2", - "mockito-target-extended-minus-junit4", - "truth", - "platform-test-annotations", - "platform-test-rules", - ], - libs: [ - "android.test.base", - "android.test.runner", - ], - jni_libs: [ - "libdexmakerjvmtiagent", - "libstaticjvmtiagent", - ], - kotlincflags: ["-Xjvm-default=all"], - optimize: { - enabled: false, - }, - test_suites: ["device-tests"], - platform_apis: true, - certificate: "platform", - aaptflags: [ - "--extra-packages", - "com.android.wm.shell", - ], - manifest: "multivalentTests/AndroidManifest.xml", -} diff --git a/libs/WindowManager/Shell/multivalentTests/Android.bp b/libs/WindowManager/Shell/multivalentTests/Android.bp new file mode 100644 index 000000000000..1686d0d54dc4 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/Android.bp @@ -0,0 +1,97 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], + default_team: "trendy_team_multitasking_windowing", +} + +android_app { + name: "WindowManagerShellRobolectric", + platform_apis: true, + static_libs: [ + "WindowManager-Shell", + ], + manifest: "AndroidManifestRobolectric.xml", + use_resource_processor: true, +} + +android_robolectric_test { + name: "WMShellRobolectricTests", + instrumentation_for: "WindowManagerShellRobolectric", + upstream: true, + java_resource_dirs: [ + "robolectric/config", + ], + srcs: [ + "src/**/*.kt", + ], + // TODO(b/323188766): Include BubbleStackViewTest once the robolectric issue is fixed. + exclude_srcs: ["src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt"], + static_libs: [ + "junit", + "androidx.test.runner", + "androidx.test.rules", + "androidx.test.ext.junit", + "mockito-robolectric-prebuilt", + "mockito-kotlin2", + "truth", + ], + auto_gen_config: true, +} + +android_test { + name: "WMShellMultivalentTestsOnDevice", + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "WindowManager-Shell", + "junit", + "androidx.test.runner", + "androidx.test.rules", + "androidx.test.ext.junit", + "frameworks-base-testutils", + "mockito-kotlin2", + "mockito-target-extended-minus-junit4", + "truth", + "platform-test-annotations", + "platform-test-rules", + ], + libs: [ + "android.test.base", + "android.test.runner", + ], + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + kotlincflags: ["-Xjvm-default=all"], + optimize: { + enabled: false, + }, + test_suites: ["device-tests"], + platform_apis: true, + certificate: "platform", + aaptflags: [ + "--extra-packages", + "com.android.wm.shell", + ], + manifest: "AndroidManifest.xml", +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java index 93893e33d2d5..ef9bf008b294 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java @@ -51,7 +51,7 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio final ILogger logger = pw::println; switch (args[0]) { case "status": { - if (android.tracing.Flags.perfettoProtolog()) { + if (android.tracing.Flags.perfettoProtologTracing()) { pw.println("(Deprecated) legacy command. Use Perfetto commands instead."); return false; } @@ -59,7 +59,7 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio return true; } case "start": { - if (android.tracing.Flags.perfettoProtolog()) { + if (android.tracing.Flags.perfettoProtologTracing()) { pw.println("(Deprecated) legacy command. Use Perfetto commands instead."); return false; } @@ -67,7 +67,7 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio return true; } case "stop": { - if (android.tracing.Flags.perfettoProtolog()) { + if (android.tracing.Flags.perfettoProtologTracing()) { pw.println("(Deprecated) legacy command. Use Perfetto commands instead."); return false; } @@ -101,7 +101,7 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio return mShellProtoLog.stopLoggingToLogcat(groups, logger) == 0; } case "save-for-bugreport": { - if (android.tracing.Flags.perfettoProtolog()) { + if (android.tracing.Flags.perfettoProtologTracing()) { pw.println("(Deprecated) legacy command"); return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index f801b0d01084..a87116ea4670 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -75,7 +75,6 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { private SurfaceControlViewHost mViewHost; private DividerHandleView mHandle; private DividerRoundedCorner mCorners; - private View mBackground; private int mTouchElevation; private VelocityTracker mVelocityTracker; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index 2b1037711249..194eb47c9360 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -277,7 +277,7 @@ public class SplitDecorManager extends WindowlessWindowManager { } @Override - public void onAnimationEnd(@androidx.annotation.NonNull Animator animation) { + public void onAnimationEnd(@NonNull Animator animation) { mRunningAnimationCount--; animT.remove(mScreenshot); animT.apply(); 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 e4213569b526..1c54754e9953 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 @@ -163,14 +163,14 @@ public class RecentTasksController implements TaskStackListenerCallback, /** * Adds a split pair. This call does not validate the taskIds, only that they are not the same. */ - public void addSplitPair(int taskId1, int taskId2, SplitBounds splitBounds) { + public boolean addSplitPair(int taskId1, int taskId2, SplitBounds splitBounds) { if (taskId1 == taskId2) { - return; + return false; } if (mSplitTasks.get(taskId1, INVALID_TASK_ID) == taskId2 && mTaskSplitBoundsMap.get(taskId1).equals(splitBounds)) { // If the two tasks are already paired and the bounds are the same, then skip updating - return; + return false; } // Remove any previous pairs removeSplitPair(taskId1); @@ -185,6 +185,7 @@ public class RecentTasksController implements TaskStackListenerCallback, notifyRecentTasksChanged(); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Add split pair: %d, %d, %s", taskId1, taskId2, splitBounds); + return true; } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java index e52235fda80f..64e26dbd70be 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java @@ -16,11 +16,14 @@ package com.android.wm.shell.splitscreen; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; + import android.content.Context; import android.view.SurfaceSession; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; @@ -50,6 +53,8 @@ class MainStage extends StageTaskListener { void activate(WindowContainerTransaction wct, boolean includingTopTask) { if (mIsActive) return; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "activate: main stage includingTopTask=%b", + includingTopTask); if (includingTopTask) { reparentTopTask(wct); @@ -64,6 +69,8 @@ class MainStage extends StageTaskListener { void deactivate(WindowContainerTransaction wct, boolean toTop) { if (!mIsActive) return; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "deactivate: main stage toTop=%b rootTaskInfo=%s", + toTop, mRootTaskInfo); mIsActive = false; if (mRootTaskInfo == null) return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index 9903113c5453..f5fbae55960a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java @@ -16,12 +16,15 @@ package com.android.wm.shell.splitscreen; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; + import android.app.ActivityManager; import android.content.Context; import android.view.SurfaceSession; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; @@ -47,6 +50,8 @@ class SideStage extends StageTaskListener { } boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove all side stage tasks: childCount=%d toTop=%b", + mChildrenTaskInfo.size(), toTop); if (mChildrenTaskInfo.size() == 0) return false; wct.reparentTasks( mRootTaskInfo.token, @@ -59,6 +64,8 @@ class SideStage extends StageTaskListener { boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) { final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove side stage task: task=%d exists=%b", taskId, + task != null); if (task == null) return false; wct.reparent(task.token, newParent, false /* onTop */); return true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index b60e361caad8..1d9fdeb92715 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -25,6 +25,8 @@ import static com.android.wm.shell.animation.Interpolators.ALPHA_IN; import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT; import static com.android.wm.shell.common.split.SplitScreenConstants.FADE_DURATION; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS; import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString; @@ -101,6 +103,7 @@ class SplitScreenTransitions { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playAnimation: transition=%d", info.getDebugId()); initTransition(transition, finishTransaction, finishCallback); final TransitSession pendingTransition = getPendingTransition(transition); @@ -123,10 +126,12 @@ class SplitScreenTransitions { playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot, topRoot); } - /** Internal funcation of playAnimation. */ + /** Internal function of playAnimation. */ private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playInternalAnimation: transition=%d", + info.getDebugId()); // Play some place-holder fade animations final boolean isEnter = isPendingEnter(transition); for (int i = info.getChanges().size() - 1; i >= 0; --i) { @@ -220,6 +225,8 @@ class SplitScreenTransitions { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull WindowContainerToken toTopRoot, @NonNull SplitDecorManager toTopDecor, @NonNull WindowContainerToken topRoot) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playDragDismissAnimation: transition=%d", + info.getDebugId()); initTransition(transition, finishTransaction, finishCallback); for (int i = info.getChanges().size() - 1; i >= 0; --i) { @@ -259,6 +266,7 @@ class SplitScreenTransitions { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playResizeAnimation: transition=%d", info.getDebugId()); initTransition(transition, finishTransaction, finishCallback); for (int i = info.getChanges().size() - 1; i >= 0; --i) { @@ -312,13 +320,15 @@ class SplitScreenTransitions { @Nullable private TransitSession getPendingTransition(IBinder transition) { if (isPendingEnter(transition)) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved enter transition"); return mPendingEnter; } else if (isPendingDismiss(transition)) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved dismiss transition"); return mPendingDismiss; } else if (isPendingResize(transition)) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved resize transition"); return mPendingResize; } - return null; } @@ -339,7 +349,7 @@ class SplitScreenTransitions { Transitions.TransitionHandler handler, int extraTransitType, boolean resizeAnim) { if (mPendingEnter != null) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " + " skip to start enter split transition since it already exist. "); return null; } @@ -355,8 +365,10 @@ class SplitScreenTransitions { mPendingEnter = new EnterSession( transition, remoteTransition, extraTransitType, resizeAnim); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " + " deduced Enter split screen"); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setEnterTransition: transitType=%d resize=%b", + extraTransitType, resizeAnim); } /** Starts a transition to dismiss split. */ @@ -364,7 +376,7 @@ class SplitScreenTransitions { Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop, @SplitScreenController.ExitReason int reason) { if (mPendingDismiss != null) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " + " skip to start dismiss split transition since it already exist. reason to " + " dismiss = %s", exitReasonToString(reason)); return null; @@ -381,9 +393,11 @@ class SplitScreenTransitions { @SplitScreenController.ExitReason int reason) { mPendingDismiss = new DismissSession(transition, reason, dismissTop); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " + " deduced Dismiss due to %s. toTop=%s", exitReasonToString(reason), stageTypeToString(dismissTop)); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setDismissTransition: reason=%s dismissTop=%s", + exitReasonToString(reason), stageTypeToString(dismissTop)); } IBinder startResizeTransition(WindowContainerTransaction wct, @@ -405,8 +419,9 @@ class SplitScreenTransitions { @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishCallback) { mPendingResize = new TransitSession(transition, consumedCallback, finishCallback); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " + " deduced Resize split screen"); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setResizeTransition"); } void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, @@ -444,12 +459,15 @@ class SplitScreenTransitions { mPendingEnter.onConsumed(aborted); mPendingEnter = null; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for enter transition"); } else if (isPendingDismiss(transition)) { mPendingDismiss.onConsumed(aborted); mPendingDismiss = null; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for dismiss transition"); } else if (isPendingResize(transition)) { mPendingResize.onConsumed(aborted); mPendingResize = null; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for resize transition"); } // TODO: handle transition consumed for active remote handler @@ -462,12 +480,15 @@ class SplitScreenTransitions { if (isPendingEnter(mAnimatingTransition)) { mPendingEnter.onFinished(wct, mFinishTransaction); mPendingEnter = null; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for enter transition"); } else if (isPendingDismiss(mAnimatingTransition)) { mPendingDismiss.onFinished(wct, mFinishTransaction); mPendingDismiss = null; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for dismiss transition"); } else if (isPendingResize(mAnimatingTransition)) { mPendingResize.onFinished(wct, mFinishTransaction); mPendingResize = null; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for resize transition"); } mActiveRemoteHandler = null; 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 2933cf48614a..7a1595fdbf01 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 @@ -44,6 +44,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; @@ -138,6 +139,7 @@ import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.shared.TransitionUtil; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason; +import com.android.wm.shell.splitscreen.SplitScreenController.SplitEnterReason; import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.LegacyTransitions; import com.android.wm.shell.transition.Transitions; @@ -313,6 +315,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Creating main/side root task"); mMainStage = new MainStage( mContext, mTaskOrganizer, @@ -454,6 +457,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition, WindowContainerTransaction wct) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "moveToStage: task=%d position=%d", task.taskId, + stagePosition); prepareEnterSplitScreen(wct, task, stagePosition, false /* resizeAnim */); if (ENABLE_SHELL_TRANSITIONS) { mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, @@ -474,6 +479,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } boolean removeFromSideStage(int taskId) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "removeFromSideStage: task=%d", taskId); final WindowContainerTransaction wct = new WindowContainerTransaction(); /** @@ -498,11 +504,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo, splitPosition, taskBounds); } - if (enteredSplitSelect) mTaskOrganizer.applyTransaction(wct); + if (enteredSplitSelect) { + mTaskOrganizer.applyTransaction(wct); + } } void startShortcut(String packageName, String shortcutId, @SplitPosition int position, Bundle options, UserHandle user) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startShortcut: pkg=%s id=%s position=%d user=%d", + packageName, shortcutId, position, user.getIdentifier()); final boolean isEnteringSplit = !isSplitActive(); IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { @@ -564,6 +574,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Use this method to launch an existing Task via a taskId */ void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startTask: task=%d position=%d", taskId, position); mSplitRequest = new SplitRequest(taskId, position); final WindowContainerTransaction wct = new WindowContainerTransaction(); options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); @@ -595,6 +606,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Launches an activity into split. */ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntent: intent=%s position=%d", intent.getIntent(), + position); mSplitRequest = new SplitRequest(intent.getIntent(), position); if (!ENABLE_SHELL_TRANSITIONS) { startIntentLegacy(intent, fillInIntent, position, options); @@ -690,6 +703,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "startTasks: task1=%d task2=%d position=%d snapPosition=%d", + taskId1, taskId2, splitPosition, snapPosition); final WindowContainerTransaction wct = new WindowContainerTransaction(); if (taskId2 == INVALID_TASK_ID) { if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) { @@ -718,6 +734,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable Bundle options1, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "startIntentAndTask: intent=%s task1=%d position=%d snapPosition=%d", + pendingIntent.getIntent(), taskId, splitPosition, snapPosition); final WindowContainerTransaction wct = new WindowContainerTransaction(); if (taskId == INVALID_TASK_ID) { options1 = options1 != null ? options1 : new Bundle(); @@ -740,6 +759,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "startShortcutAndTask: shortcut=%s task1=%d position=%d snapPosition=%d", + shortcutInfo, taskId, splitPosition, snapPosition); final WindowContainerTransaction wct = new WindowContainerTransaction(); if (taskId == INVALID_TASK_ID) { options1 = options1 != null ? options1 : new Bundle(); @@ -801,6 +823,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "startIntents: intent1=%s intent2=%s position=%d snapPosition=%d", + pendingIntent1.getIntent(), pendingIntent2.getIntent(), splitPosition, + snapPosition); final WindowContainerTransaction wct = new WindowContainerTransaction(); if (pendingIntent2 == null) { options1 = options1 != null ? options1 : new Bundle(); @@ -1302,6 +1328,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } void switchSplitPosition(String reason) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "switchSplitPosition"); final SurfaceControl.Transaction t = mTransactionPool.acquire(); mTempRect1.setEmpty(); final StageTaskListener topLeftStage = @@ -1343,7 +1370,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, }); }); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason); + ProtoLog.v(WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason); mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(), getSideStagePosition(), mSideStage.getTopChildTaskUid(), mSplitLayout.isLeftRightSplit()); @@ -1376,11 +1403,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!mMainStage.isActive()) { return; } - + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onKeyguardVisibilityChanged: showing=%b", showing); setDividerVisibility(!mKeyguardShowing, null); } void onFinishedWakingUp() { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinishedWakingUp"); if (!mMainStage.isActive()) { return; } @@ -1421,6 +1449,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: topTaskId=%d reason=%s active=%b", + toTopTaskId, exitReasonToString(exitReason), mMainStage.isActive()); if (!mMainStage.isActive()) return; StageTaskListener childrenToTop = null; @@ -1439,6 +1469,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void exitSplitScreen(@Nullable StageTaskListener childrenToTop, @ExitReason int exitReason) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: mainStageToTop=%b reason=%s active=%b", + childrenToTop == mMainStage, exitReasonToString(exitReason), mMainStage.isActive()); if (!mMainStage.isActive()) return; final WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -1447,6 +1479,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void applyExitSplitScreen(@Nullable StageTaskListener childrenToTop, WindowContainerTransaction wct, @ExitReason int exitReason) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "applyExitSplitScreen: reason=%s", + exitReasonToString(exitReason)); if (!mMainStage.isActive() || mIsExiting) return; onSplitScreenExit(); @@ -1502,7 +1536,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } }); - Slog.i(TAG, "applyExitSplitScreen, reason = " + exitReasonToString(exitReason)); // Log the exit if (childrenToTop != null) { logExitToStage(exitReason, childrenToTop == mMainStage); @@ -1527,6 +1560,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * Exits the split screen by finishing one of the tasks. */ protected void exitStage(@SplitPosition int stageToClose) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitStage: stageToClose=%d", stageToClose); mSplitLayout.flingDividerToDismiss(stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT, EXIT_REASON_APP_FINISHED); } @@ -1540,12 +1574,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, try { activityTaskManagerService.setFocusedTask(getTaskId(stageToFocus)); } catch (RemoteException | NullPointerException e) { - ProtoLog.e(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + ProtoLog.e(WM_SHELL_SPLIT_SCREEN, "Unable to update focus on the chosen stage: %s", e.getMessage()); } } private void clearRequestIfPresented() { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearRequestIfPresented"); if (mSideStageListener.mVisible && mSideStageListener.mHasChildren && mMainStageListener.mVisible && mSideStageListener.mHasChildren) { mSplitRequest = null; @@ -1581,6 +1616,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void clearSplitPairedInRecents(@ExitReason int exitReason) { if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearSplitPairedInRecents: reason=%s", + exitReasonToString(exitReason)); mRecentTasks.ifPresent(recentTasks -> { // Notify recents if we are exiting in a way that breaks the pair, and disable further // updates to splits in the recents until we enter split again @@ -1597,11 +1634,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void prepareExitSplitScreen(@StageType int stageToTop, @NonNull WindowContainerTransaction wct) { if (!mMainStage.isActive()) return; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareExitSplitScreen: stageToTop=%d", stageToTop); mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE); mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN); } private void prepareEnterSplitScreen(WindowContainerTransaction wct) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen"); prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED, !mIsDropEntering); } @@ -1613,6 +1652,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void prepareEnterSplitScreen(WindowContainerTransaction wct, @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, boolean resizeAnim) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen: position=%d resize=%b", + startPosition, resizeAnim); onSplitScreenEnter(); // Preemptively reset the reparenting behavior if we know that we are entering, as starting // split tasks with activity trampolines can inadvertently trigger the task to be @@ -1629,6 +1670,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void prepareBringSplit(WindowContainerTransaction wct, @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, boolean resizeAnim) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareBringSplit: task=%d isSplitVisible=%b", + taskInfo != null ? taskInfo.taskId : -1, isSplitScreenVisible()); if (taskInfo != null) { wct.startTask(taskInfo.taskId, resolveStartStage(STAGE_TYPE_UNDEFINED, startPosition, null, wct)); @@ -1649,6 +1692,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void prepareActiveSplit(WindowContainerTransaction wct, @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition, boolean resizeAnim) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareActiveSplit: task=%d isSplitVisible=%b", + taskInfo != null ? taskInfo.taskId : -1, isSplitScreenVisible()); if (!ENABLE_SHELL_TRANSITIONS) { // Legacy transition we need to create divider here, shell transition case we will // create it on #finishEnterSplitScreen @@ -1667,6 +1712,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } private void prepareSplitLayout(WindowContainerTransaction wct, boolean resizeAnim) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareSplitLayout: resize=%b", resizeAnim); if (resizeAnim) { mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT); } else { @@ -1686,6 +1732,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } void finishEnterSplitScreen(SurfaceControl.Transaction finishT) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen"); mSplitLayout.update(finishT, true /* resetImePosition */); mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash, getMainStageBounds()); @@ -1835,12 +1882,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, leftTopTaskId, rightBottomTaskId, mSplitLayout.calculateCurrentSnapPosition()); if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) { // Update the pair for the top tasks - recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, splitBounds); + boolean added = recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, + splitBounds); + if (added) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "updateRecentTasksSplitPair: adding split pair ltTask=%d rbTask=%d", + leftTopTaskId, rightBottomTaskId); + } } }); } private void sendSplitVisibilityChanged() { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "sendSplitVisibilityChanged: dividerVisible=%b", + mDividerVisible); for (int i = mListeners.size() - 1; i >= 0; --i) { final SplitScreen.SplitScreenListener l = mListeners.get(i); l.onSplitVisibilityChanged(mDividerVisible); @@ -1855,6 +1910,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, throw new IllegalArgumentException(this + "\n Unknown task appeared: " + taskInfo); } + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%s", taskInfo); mRootTaskInfo = taskInfo; mRootTaskLeash = leash; @@ -1880,6 +1936,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mSplitLayout != null && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration) && mMainStage.isActive()) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: task=%d updating", + taskInfo.taskId); // Clear the divider remote animating flag as the divider will be re-rendered to apply // the new rotation config. Don't reset the IME state since those updates are not in // sync with task info changes. @@ -1892,6 +1950,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override @CallSuper public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%s", taskInfo); if (mRootTaskInfo == null) { throw new IllegalArgumentException(this + "\n Unknown task vanished: " + taskInfo); } @@ -1911,6 +1970,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @VisibleForTesting void onRootTaskAppeared() { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskAppeared: rootTask=%s mainRoot=%b sideRoot=%b", + mRootTaskInfo, mMainStageListener.mHasRootTask, mSideStageListener.mHasRootTask); // Wait unit all root tasks appeared. if (mRootTaskInfo == null || !mMainStageListener.mHasRootTask @@ -1937,6 +1998,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * #onStageHasChildrenChanged because this would be called every time child task appeared. * NOTICE: This only be called on legacy transition. */ private void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onChildTaskAppeared: isMainStage=%b task=%d", + stageListener == mMainStageListener, taskId); // Handle entering split screen while there is a split pair running in the background. if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive() && mSplitRequest == null) { @@ -1960,6 +2023,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } private void onRootTaskVanished() { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskVanished"); final WindowContainerTransaction wct = new WindowContainerTransaction(); mLaunchAdjacentController.clearLaunchAdjacentRoot(); applyExitSplitScreen(null /* childrenToTop */, wct, EXIT_REASON_ROOT_TASK_VANISHED); @@ -1990,6 +2054,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return; } + // TODO Protolog + // Check if it needs to dismiss split screen when both stage invisible. if (!mainStageVisible && mExitSplitScreenOnHide) { exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME); @@ -2020,14 +2086,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return; } - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, - "Request to %s divider bar from %s.", - (visible ? "show" : "hide"), Debug.getCaller()); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "setDividerVisibility: visible=%b keyguardShowing=%b dividerAnimating=%b caller=%s", + visible, mKeyguardShowing, mIsDividerRemoteAnimating, Debug.getCaller()); // Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard // dismissing animation. if (visible && mKeyguardShowing) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, " Defer showing divider bar due to keyguard showing."); return; } @@ -2036,7 +2102,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, sendSplitVisibilityChanged(); if (mIsDividerRemoteAnimating) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, " Skip animating divider bar due to it's remote animating."); return; } @@ -2050,12 +2116,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void applyDividerVisibility(@Nullable SurfaceControl.Transaction t) { final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash(); if (dividerLeash == null) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, " Skip animating divider bar due to divider leash not ready."); return; } if (mIsDividerRemoteAnimating) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, " Skip animating divider bar due to it's remote animating."); return; } @@ -2119,6 +2185,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Callback when split roots have child or haven't under it. * NOTICE: This only be called on legacy transition. */ private void onStageHasChildrenChanged(StageListenerImpl stageListener) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onStageHasChildrenChanged: isMainStage=%b", + stageListener == mMainStageListener); final boolean hasChildren = stageListener.mHasChildren; final boolean isSideStage = stageListener == mSideStageListener; if (!hasChildren && !mIsExiting && mMainStage.isActive()) { @@ -2170,13 +2238,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override - public void onSnappedToDismiss(boolean bottomOrRight, int reason) { + public void onSnappedToDismiss(boolean bottomOrRight, @ExitReason int exitReason) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onSnappedToDismiss: bottomOrRight=%b reason=%s", + bottomOrRight, exitReasonToString(exitReason)); final boolean mainStageToTop = bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT; final StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage; if (!ENABLE_SHELL_TRANSITIONS) { - exitSplitScreen(toTopStage, reason); + exitSplitScreen(toTopStage, exitReason); return; } @@ -2219,6 +2289,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onLayoutSizeChanged(SplitLayout layout) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onLayoutSizeChanged"); // Reset this flag every time onLayoutSizeChanged. mShowDecorImmediately = false; @@ -2278,8 +2349,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; final StageTaskListener bottomRightStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; - return layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, + boolean updated = layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "updateWindowBounds: topLeftStage=%s bottomRightStage=%s", + layout.getBounds1(), layout.getBounds2()); + return updated; } void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t, @@ -2291,6 +2365,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer, applyResizingOffset); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "updateSurfaceBounds: topLeftStage=%s bottomRightStage=%s", + layout.getBounds1(), layout.getBounds2()); } @Override @@ -2329,6 +2406,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setLayoutOffsetTarget: x=%d y=%d", + offsetX, offsetY); final StageTaskListener topLeftStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; final StageTaskListener bottomRightStage = @@ -2343,6 +2422,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (displayId != DEFAULT_DISPLAY) { return; } + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onDisplayAdded: display=%d", displayId); mDisplayController.addDisplayChangingController(this::onDisplayChange); } @@ -2357,8 +2437,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void onDisplayChange(int displayId, int fromRotation, int toRotation, @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) { - if (displayId != DEFAULT_DISPLAY || !mMainStage.isActive()) return; + if (displayId != DEFAULT_DISPLAY || !mMainStage.isActive()) { + return; + } + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "onDisplayChange: display=%d fromRot=%d toRot=%d config=%s", + displayId, fromRotation, toRotation, + newDisplayAreaInfo != null ? newDisplayAreaInfo.configuration : null); mSplitLayout.rotateTo(toRotation); if (newDisplayAreaInfo != null) { mSplitLayout.updateConfiguration(newDisplayAreaInfo.configuration); @@ -2369,6 +2455,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @VisibleForTesting void onFoldedStateChanged(boolean folded) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFoldedStateChanged: folded=%b", folded); mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; if (!folded) return; @@ -2439,6 +2526,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); if (triggerTask == null) { if (isSplitActive()) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d display rotation", + request.getDebugId()); // Check if the display is rotating. final TransitionRequestInfo.DisplayChange displayChange = request.getDisplayChange(); @@ -2467,6 +2556,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } if (isSplitActive()) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d split active", + request.getDebugId()); // Try to handle everything while in split-screen, so return a WCT even if it's empty. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split" + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d" @@ -2541,6 +2632,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return null; } else { if (isOpening && getStageOfTask(triggerTask) != null) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d enter split", + request.getDebugId()); // One task is appearing into split, prepare to enter split screen. out = new WindowContainerTransaction(); prepareEnterSplitScreen(out); @@ -2557,6 +2650,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, */ public void addEnterOrExitIfNeeded(@Nullable TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "addEnterOrExitIfNeeded: transition=%d", + request.getDebugId()); final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); if (triggerTask != null && triggerTask.displayId != mDisplayId) { // Skip handling task on the other display. @@ -2591,6 +2686,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, public void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "mergeAnimation: transition=%d", info.getDebugId()); mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } @@ -2602,6 +2698,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed"); mSplitTransitions.onTransitionConsumed(transition, aborted, finishT); } @@ -2617,6 +2714,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // If we're not in split-mode, just abort so something else can handle it. if (!mMainStage.isActive()) return false; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startAnimation: transition=%d", info.getDebugId()); mSplitLayout.setFreezeDividerWindow(false); final StageChangeRecord record = new StageChangeRecord(); final int transitType = info.getType(); @@ -2727,6 +2825,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info, startTransaction, finishTransaction, finishCallback)) { if (mSplitTransitions.isPendingResize(transition)) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "startAnimation: transition=%d display change", info.getDebugId()); // Only need to update in resize because divider exist before transition. mSplitLayout.update(startTransaction, true /* resetImePosition */); startTransaction.apply(); @@ -2797,6 +2897,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startPendingAnimation: transition=%d", + info.getDebugId()); boolean shouldAnimate = true; if (mSplitTransitions.isPendingEnter(transition)) { shouldAnimate = startPendingEnterAnimation(transition, @@ -2830,6 +2932,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Called to clean-up state and do house-keeping after the animation is done. */ public void onTransitionAnimationComplete() { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionAnimationComplete"); // If still playing, let it finish. if (!mMainStage.isActive() && !mIsExiting) { // Update divider state after animation so that it is still around and positioned @@ -2842,6 +2945,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @NonNull SplitScreenTransitions.EnterSession enterTransition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startPendingEnterAnimation: enterTransition=%s", + enterTransition); // First, verify that we actually have opened apps in both splits. TransitionInfo.Change mainChild = null; TransitionInfo.Change sideChild = null; @@ -2959,6 +3064,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } public void goToFullscreenFromSplit() { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "goToFullscreenFromSplit"); // If main stage is focused, toEnd = true if // mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT. Otherwise toEnd = false // If side stage is focused, toEnd = true if @@ -2974,6 +3080,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Move the specified task to fullscreen, regardless of focus state. */ public void moveTaskToFullscreen(int taskId, int exitReason) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "moveTaskToFullscreen"); boolean leftOrTop; if (mMainStage.containsTask(taskId)) { leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT); @@ -2994,6 +3101,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, */ public void onPipExpandToSplit(WindowContainerTransaction wct, ActivityManager.RunningTaskInfo taskInfo) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onPipExpandToSplit: task=%s", taskInfo); prepareEnterSplitScreen(wct, taskInfo, getActivateSplitPosition(taskInfo), false /*resizeAnim*/); @@ -3040,6 +3148,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "prepareDismissAnimation: transition=%d toStage=%d reason=%s", + info.getDebugId(), toStage, exitReasonToString(dismissReason)); // Make some noise if things aren't totally expected. These states shouldn't effect // transitions locally, but remotes (like Launcher) may get confused if they were // depending on listener callbacks. This can happen because task-organizer callbacks @@ -3126,6 +3237,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @NonNull SplitScreenTransitions.DismissSession dismissTransition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "startPendingDismissAnimation: transition=%d dismissTransition=%s", + info.getDebugId(), dismissTransition); prepareDismissAnimation(dismissTransition.mDismissTop, dismissTransition.mReason, info, t, finishT); if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) { @@ -3146,6 +3260,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Call this when starting the open-recents animation while split-screen is active. */ public void onRecentsInSplitAnimationStart(TransitionInfo info) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationStart: transition=%d", + info.getDebugId()); if (isSplitScreenVisible()) { // Cache tasks on live tile. for (int i = 0; i < info.getChanges().size(); ++i) { @@ -3178,6 +3294,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Call this when the recents animation during split-screen finishes. */ public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationFinish"); mPausingTasks.clear(); // Check if the recent transition is finished by returning to the current // split, so we can restore the divider bar. @@ -3203,6 +3320,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Call this when the recents animation finishes by doing pair-to-pair switch. */ public void onRecentsPairToPairAnimationFinish(WindowContainerTransaction finishWct) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsPairToPairAnimationFinish"); // Pair-to-pair switch happened so here should evict the live tile from its stage. // Otherwise, the task will remain in stage, and occluding the new task when next time // user entering recents. @@ -3284,6 +3402,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * handled. */ private void setSplitsVisible(boolean visible) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setSplitsVisible: visible=%b", visible); mMainStageListener.mVisible = mSideStageListener.mVisible = visible; mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible; } @@ -3292,6 +3411,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * Sets drag info to be logged when splitscreen is next entered. */ public void onDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onDroppedToSplit: position=%d", position); if (!isSplitScreenVisible()) { mIsDropEntering = true; mSkipEvictingMainStageChildren = true; @@ -3308,7 +3428,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** * Sets info to be logged when splitscreen is next entered. */ - public void onRequestToSplit(InstanceId sessionId, int enterReason) { + public void onRequestToSplit(InstanceId sessionId, @SplitEnterReason int enterReason) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRequestToSplit: reason=%d", enterReason); if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) { // If split running background, exit split first. // Skip this on shell transition due to we could evict existing tasks on transition @@ -3384,6 +3505,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onNoLongerSupportMultiWindow: task=%s", taskInfo); if (mMainStage.isActive()) { final boolean isMainStage = mMainStageListener == this; if (!ENABLE_SHELL_TRANSITIONS) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index af7bf360f036..f33ab33dafcc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -25,6 +25,7 @@ import static android.view.RemoteAnimationTarget.MODE_OPENING; import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; import android.annotation.CallSuper; @@ -44,6 +45,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; +import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; @@ -175,6 +177,9 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { @Override @CallSuper public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%d taskParent=%d rootTask=%d", + taskInfo.taskId, taskInfo.parentTaskId, + mRootTaskInfo != null ? mRootTaskInfo.taskId : -1); if (mRootTaskInfo == null) { mRootLeash = leash; mRootTaskInfo = taskInfo; @@ -225,6 +230,9 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { || !ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType()) || !ArrayUtils.contains(CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, taskInfo.getWindowingMode())) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "onTaskInfoChanged: task=%d no longer supports multiwindow", + taskInfo.taskId); // Leave split screen if the task no longer supports multi window or have // uncontrolled task. mCallbacks.onNoLongerSupportMultiWindow(taskInfo); @@ -251,6 +259,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { @Override @CallSuper public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%d", taskInfo.taskId); final int taskId = taskInfo.taskId; if (mRootTaskInfo.taskId == taskId) { mCallbacks.onRootTaskVanished(); @@ -333,6 +342,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "addTask: task=%d", task.taskId); // Clear overridden bounds and windowing mode to make sure the child task can inherit // windowing mode and bounds from split root. wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED) @@ -342,6 +352,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "reorderChild: task=%d onTop=%b", taskId, onTop); if (!containsTask(taskId)) { return; } @@ -357,6 +368,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { /** Collects all the current child tasks and prepares transaction to evict them to display. */ void evictAllChildren(WindowContainerTransaction wct) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evicting all children"); for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); @@ -367,11 +379,13 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); if (taskId == taskInfo.taskId) continue; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict other child: task=%d", taskId); wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); } } void evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "evictNonOpeningChildren"); final SparseArray<ActivityManager.RunningTaskInfo> toBeEvict = mChildrenTaskInfo.clone(); for (int i = 0; i < apps.length; i++) { if (apps[i].mode == MODE_OPENING) { @@ -380,6 +394,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } for (int i = toBeEvict.size() - 1; i >= 0; i--) { final ActivityManager.RunningTaskInfo taskInfo = toBeEvict.valueAt(i); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict non-opening child: task=%d", taskInfo.taskId); wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); } } @@ -388,12 +403,15 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); if (!taskInfo.isVisible) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict invisible child: task=%d", + taskInfo.taskId); wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); } } } void evictChildren(WindowContainerTransaction wct, int taskId) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict child: task=%d", taskId); final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.get(taskId); if (taskInfo != null) { wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); diff --git a/media/tests/projection/Android.bp b/media/tests/projection/Android.bp index c9a886415c6d..fd5f19535537 100644 --- a/media/tests/projection/Android.bp +++ b/media/tests/projection/Android.bp @@ -3,6 +3,7 @@ //######################################################################## package { + default_team: "trendy_team_lse_desktop_os_experience", // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "frameworks_base_license" diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java index 3383f3bd4e7a..994f4ae1c2e3 100644 --- a/nfc/java/android/nfc/cardemulation/PollingFrame.java +++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java @@ -201,7 +201,7 @@ public final class PollingFrame implements Parcelable{ /** * Returns the timestamp of when the polling loop frame was observed in milliseconds. These - * timestamps are relative and not absolute and should only be used fro comparing the timing of + * timestamps are relative and not absolute and should only be used for comparing the timing of * frames relative to each other. * @return the timestamp in milliseconds */ diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 82b47a964204..527701c06bc6 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -63,9 +63,9 @@ <!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] --> <string name="choose_provider_body">Select a password manager to save your info and sign in faster next time</string> <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is passkey. [CHAR LIMIT=200] --> - <string name="choose_create_option_passkey_title">Create passkey for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string> + <string name="choose_create_option_passkey_title">Create passkey to sign in to <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string> <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is password. [CHAR LIMIT=200] --> - <string name="choose_create_option_password_title">Save password for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string> + <string name="choose_create_option_password_title">Save password to sign in to <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string> <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] --> <string name="choose_create_option_sign_in_title">Save sign-in info for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string> <!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] --> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 13260231038d..c118f886a331 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -237,11 +237,8 @@ class CredentialAutofillService : AutofillService() { if (providerList.isEmpty()) { return false } - var totalEntryCount = 0 - providerList.forEach { provider -> - totalEntryCount += provider.credentialEntryList.size - } val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerList) + var totalEntryCount = providerDisplayInfo.sortedUserNameToCredentialEntryList.size val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0 val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs diff --git a/packages/PackageInstaller/res/layout/install_content_view.xml b/packages/PackageInstaller/res/layout/install_content_view.xml index 2ecd2d55ac71..524a88a638ad 100644 --- a/packages/PackageInstaller/res/layout/install_content_view.xml +++ b/packages/PackageInstaller/res/layout/install_content_view.xml @@ -24,114 +24,116 @@ android:paddingLeft="?android:attr/dialogPreferredPadding" android:paddingRight="?android:attr/dialogPreferredPadding"> - <LinearLayout - android:id="@+id/staging" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:visibility="invisible"> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" - android:text="@string/message_staging" /> - - <ProgressBar - android:id="@+id/progress_indeterminate" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="8dp" - style="?android:attr/progressBarStyleHorizontal" - android:indeterminate="true" /> - - </LinearLayout> - - <LinearLayout - android:id="@+id/installing" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:visibility="invisible"> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" - android:text="@string/installing" /> - - <ProgressBar - android:id="@+id/progress" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="8dp" - style="?android:attr/progressBarStyleHorizontal" - android:indeterminate="true" /> - - </LinearLayout> - - <TextView - android:id="@+id/install_confirm_question" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" - android:text="@string/install_confirm_question" - android:visibility="invisible" /> - - <TextView - android:id="@+id/install_confirm_question_update" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" - android:text="@string/install_confirm_question_update" - android:visibility="invisible" /> + <LinearLayout + android:id="@+id/staging" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:visibility="invisible"> <TextView - android:id="@+id/install_success" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@android:style/TextAppearance.Material.Subhead" - android:text="@string/install_done" - android:visibility="invisible" /> + android:text="@string/message_staging" /> - <TextView - android:id="@+id/install_failed" - android:layout_width="wrap_content" + <ProgressBar + android:id="@+id/progress_indeterminate" + android:layout_width="match_parent" android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" - android:text="@string/install_failed" - android:visibility="invisible" /> + android:paddingTop="8dp" + style="?android:attr/progressBarStyleHorizontal" + android:indeterminate="true" /> - <TextView - android:id="@+id/install_failed_blocked" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" - android:text="@string/install_failed_blocked" - android:visibility="invisible" /> + </LinearLayout> - <TextView - android:id="@+id/install_failed_conflict" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" - android:text="@string/install_failed_conflict" - android:visibility="invisible" /> + <LinearLayout + android:id="@+id/installing" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:visibility="invisible"> <TextView - android:id="@+id/install_failed_incompatible" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@android:style/TextAppearance.Material.Subhead" - android:text="@string/install_failed_incompatible" - android:visibility="invisible" /> + android:text="@string/installing" /> - <TextView - android:id="@+id/install_failed_invalid_apk" - android:layout_width="wrap_content" + <ProgressBar + android:id="@+id/progress" + android:layout_width="match_parent" android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" - android:text="@string/install_failed_invalid_apk" - android:visibility="invisible" /> - -</FrameLayout> + android:paddingTop="8dp" + style="?android:attr/progressBarStyleHorizontal" + android:indeterminate="true" /> + + </LinearLayout> + + <TextView + android:id="@+id/install_confirm_question" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@android:style/TextAppearance.Material.Subhead" + android:text="@string/install_confirm_question" + android:visibility="invisible" + android:scrollbars="vertical" /> + + <TextView + android:id="@+id/install_confirm_question_update" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@android:style/TextAppearance.Material.Subhead" + android:text="@string/install_confirm_question_update" + android:visibility="invisible" + android:scrollbars="vertical" /> + + <TextView + android:id="@+id/install_success" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@android:style/TextAppearance.Material.Subhead" + android:text="@string/install_done" + android:visibility="invisible" /> + + <TextView + android:id="@+id/install_failed" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@android:style/TextAppearance.Material.Subhead" + android:text="@string/install_failed" + android:visibility="invisible" /> + + <TextView + android:id="@+id/install_failed_blocked" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@android:style/TextAppearance.Material.Subhead" + android:text="@string/install_failed_blocked" + android:visibility="invisible" /> + + <TextView + android:id="@+id/install_failed_conflict" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@android:style/TextAppearance.Material.Subhead" + android:text="@string/install_failed_conflict" + android:visibility="invisible" /> + + <TextView + android:id="@+id/install_failed_incompatible" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@android:style/TextAppearance.Material.Subhead" + android:text="@string/install_failed_incompatible" + android:visibility="invisible" /> + + <TextView + android:id="@+id/install_failed_invalid_apk" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@android:style/TextAppearance.Material.Subhead" + android:text="@string/install_failed_invalid_apk" + android:visibility="invisible" /> + +</FrameLayout>
\ No newline at end of file diff --git a/packages/PackageInstaller/res/layout/uninstall_content_view.xml b/packages/PackageInstaller/res/layout/uninstall_content_view.xml index 5666c0e44e0b..434e33323ba1 100644 --- a/packages/PackageInstaller/res/layout/uninstall_content_view.xml +++ b/packages/PackageInstaller/res/layout/uninstall_content_view.xml @@ -18,31 +18,36 @@ <!-- Check box that is displayed in the activity resolver UI for the user to make their selection the preferred activity. --> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:theme="?android:attr/alertDialogTheme" - android:orientation="vertical" - android:paddingTop="8dp" - android:paddingStart="?android:attr/dialogPreferredPadding" - android:paddingEnd="?android:attr/dialogPreferredPadding" - android:clipToPadding="false"> + android:layout_height="wrap_content"> - <TextView - android:id="@+id/message" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" /> + <LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:theme="?android:attr/alertDialogTheme" + android:orientation="vertical" + android:paddingTop="8dp" + android:paddingStart="?android:attr/dialogPreferredPadding" + android:paddingEnd="?android:attr/dialogPreferredPadding" + android:clipToPadding="false"> - <CheckBox - android:id="@+id/keepData" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:layout_marginStart="-8dp" - android:paddingLeft="8sp" - android:visibility="gone" - style="@android:style/TextAppearance.Material.Subhead" /> + <TextView + android:id="@+id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@android:style/TextAppearance.Material.Subhead" /> -</LinearLayout>
\ No newline at end of file + <CheckBox + android:id="@+id/keepData" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginStart="-8dp" + android:paddingLeft="8sp" + android:visibility="gone" + style="@android:style/TextAppearance.Material.Subhead" /> + + </LinearLayout> +</ScrollView> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index cf6aab641fc9..e95a8e63d644 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -51,6 +51,7 @@ import android.provider.Settings; import android.text.Html; import android.text.Spanned; import android.text.TextUtils; +import android.text.method.ScrollingMovementMethod; import android.util.Log; import android.view.View; import android.widget.Button; @@ -174,6 +175,7 @@ public class PackageInstallerActivity extends Activity { } viewToEnable.setVisibility(View.VISIBLE); + viewToEnable.setMovementMethod(new ScrollingMovementMethod()); mEnableOk = true; mOk.setEnabled(true); diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java index dbe32cc42d1a..0a4aa48fc126 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.text.Html; +import android.text.method.ScrollingMovementMethod; import android.view.View; import android.widget.TextView; import androidx.annotation.NonNull; @@ -94,6 +95,7 @@ public class InstallConfirmationFragment extends DialogFragment { viewToEnable = dialogView.requireViewById(R.id.install_confirm_question); } viewToEnable.setVisibility(View.VISIBLE); + viewToEnable.setMovementMethod(new ScrollingMovementMethod()); return mDialog; } diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS index 5f5f1d59ac1c..5966c9f759fb 100644 --- a/packages/SettingsLib/OWNERS +++ b/packages/SettingsLib/OWNERS @@ -5,6 +5,7 @@ cipson@google.com dsandler@android.com edgarwang@google.com evanlaird@google.com +jiannan@google.com juliacr@google.com ykhung@google.com diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt index bb7e8575ba1b..3acf075d8900 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt @@ -18,7 +18,6 @@ package com.android.settingslib.spa.widget.preference import androidx.compose.foundation.clickable import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import com.android.settingslib.spa.framework.common.EntryMacro @@ -107,14 +106,9 @@ fun Preference( ) { val onClickWithLog = wrapOnClickWithLog(model.onClick) val enabled = model.enabled() - val modifier = remember(enabled) { - if (onClickWithLog != null) { - Modifier.clickable( - enabled = enabled, - onClick = onClickWithLog - ) - } else Modifier - } + val modifier = if (onClickWithLog != null) { + Modifier.clickable(enabled = enabled, onClick = onClickWithLog) + } else Modifier EntryHighlight { BasePreference( title = model.title, diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index ba7738005de2..cb6894eb87ba 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -563,3 +563,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "media_controls_refactor" + namespace: "systemui" + description: "Refactors media code to follow the recommended architecture" + bug: "326408371" +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt index 452dc03facfd..d23cd0c06aab 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt @@ -35,11 +35,13 @@ import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSect import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection import com.android.systemui.keyguard.ui.composable.section.LockSection +import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection import com.android.systemui.keyguard.ui.composable.section.NotificationSection import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection import com.android.systemui.keyguard.ui.composable.section.StatusBarSection import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel +import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.res.R import dagger.Binds import dagger.Module @@ -63,6 +65,7 @@ constructor( private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>, private val bottomAreaSection: BottomAreaSection, private val settingsMenuSection: SettingsMenuSection, + private val mediaCarouselSection: MediaCarouselSection, private val clockInteractor: KeyguardClockInteractor, ) : ComposableLockscreenSceneBlueprint { @@ -112,10 +115,16 @@ constructor( if (viewModel.isLargeClockVisible) { Spacer(modifier = Modifier.weight(weight = 1f)) - with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) } + with(clockSection) { + LargeClock( + modifier = Modifier.fillMaxWidth(), + ) + } } - if (viewModel.areNotificationsVisible) { + with(mediaCarouselSection) { MediaCarousel() } + + if (viewModel.areNotificationsVisible(resources = resources)) { with(notificationSection) { Notifications( modifier = Modifier.fillMaxWidth().weight(weight = 1f) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt index 71c60c70a655..c422c4b58b55 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt @@ -35,6 +35,7 @@ import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSect import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection import com.android.systemui.keyguard.ui.composable.section.LockSection +import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection import com.android.systemui.keyguard.ui.composable.section.NotificationSection import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection @@ -63,6 +64,7 @@ constructor( private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>, private val bottomAreaSection: BottomAreaSection, private val settingsMenuSection: SettingsMenuSection, + private val mediaCarouselSection: MediaCarouselSection, private val clockInteractor: KeyguardClockInteractor, ) : ComposableLockscreenSceneBlueprint { @@ -115,7 +117,9 @@ constructor( with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) } } - if (viewModel.areNotificationsVisible) { + with(mediaCarouselSection) { MediaCarousel() } + + if (viewModel.areNotificationsVisible(resources = resources)) { with(notificationSection) { Notifications( modifier = Modifier.fillMaxWidth().weight(weight = 1f) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt index af836b68544c..d2184252102b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt @@ -41,6 +41,7 @@ import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSect import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection import com.android.systemui.keyguard.ui.composable.section.LockSection +import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection import com.android.systemui.keyguard.ui.composable.section.NotificationSection import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection @@ -70,6 +71,7 @@ constructor( private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>, private val bottomAreaSection: BottomAreaSection, private val settingsMenuSection: SettingsMenuSection, + private val mediaCarouselSection: MediaCarouselSection, private val clockInteractor: KeyguardClockInteractor, private val largeScreenHeaderHelper: LargeScreenHeaderHelper, ) : ComposableLockscreenSceneBlueprint { @@ -100,6 +102,14 @@ constructor( modifier = Modifier.fillMaxHeight().weight(weight = 1f), horizontalAlignment = Alignment.CenterHorizontally, ) { + with(clockSection) { + SmallClock( + burnInParams = burnIn.parameters, + onTopChanged = burnIn.onSmallClockTopChanged, + modifier = Modifier.fillMaxWidth(), + ) + } + with(smartSpaceSection) { SmartSpace( burnInParams = burnIn.parameters, @@ -121,9 +131,13 @@ constructor( ) } - Spacer(modifier = Modifier.weight(weight = 1f)) - with(clockSection) { LargeClock() } - Spacer(modifier = Modifier.weight(weight = 1f)) + if (viewModel.isLargeClockVisible) { + Spacer(modifier = Modifier.weight(weight = 1f)) + with(clockSection) { LargeClock() } + Spacer(modifier = Modifier.weight(weight = 1f)) + } + + with(mediaCarouselSection) { MediaCarousel() } } with(notificationSection) { val splitShadeTopMargin: Dp = diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt index e2e7a950cdfd..f86623fe935c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt @@ -41,12 +41,14 @@ import com.android.systemui.keyguard.ui.composable.LockscreenLongPress import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection import com.android.systemui.keyguard.ui.composable.section.LockSection +import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection import com.android.systemui.keyguard.ui.composable.section.NotificationSection import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection import com.android.systemui.keyguard.ui.composable.section.StatusBarSection import com.android.systemui.keyguard.ui.composable.section.WeatherClockSection import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel +import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.res.R import com.android.systemui.shade.LargeScreenHeaderHelper import dagger.Binds @@ -68,6 +70,7 @@ constructor( private val bottomAreaSection: BottomAreaSection, private val settingsMenuSection: SettingsMenuSection, private val clockInteractor: KeyguardClockInteractor, + private val mediaCarouselSection: MediaCarouselSection, ) : ComposableLockscreenSceneBlueprint { override val id: String = WEATHER_CLOCK_BLUEPRINT_ID @@ -107,7 +110,9 @@ constructor( ) } - if (viewModel.areNotificationsVisible) { + with(mediaCarouselSection) { MediaCarousel() } + + if (viewModel.areNotificationsVisible(resources = resources)) { with(notificationSection) { Notifications( modifier = Modifier.fillMaxWidth().weight(weight = 1f) @@ -228,6 +233,7 @@ constructor( private val clockInteractor: KeyguardClockInteractor, private val largeScreenHeaderHelper: LargeScreenHeaderHelper, private val weatherClockSection: WeatherClockSection, + private val mediaCarouselSection: MediaCarouselSection, ) : ComposableLockscreenSceneBlueprint { override val id: String = SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID @@ -276,6 +282,8 @@ constructor( ), ) } + + with(mediaCarouselSection) { MediaCarousel() } } with(notificationSection) { val splitShadeTopMargin: Dp = diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt index 335c915411ee..152cc67f6c9e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt @@ -39,7 +39,6 @@ import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChange import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel -import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import javax.inject.Inject /** Provides small clock and large clock composables for the default clock face. */ @@ -49,7 +48,6 @@ constructor( private val viewModel: KeyguardClockViewModel, private val clockInteractor: KeyguardClockInteractor, private val aodBurnInViewModel: AodBurnInViewModel, - private val lockscreenSmartspaceController: LockscreenSmartspaceController, ) { @Composable @@ -62,15 +60,11 @@ constructor( val currentClock by viewModel.currentClock.collectAsState() viewModel.clock = currentClock - if (clockSize != KeyguardClockSwitch.SMALL) { + if (clockSize != KeyguardClockSwitch.SMALL || currentClock?.smallClock?.view == null) { onTopChanged(null) return } - if (currentClock?.smallClock?.view == null) { - return - } - val view = LocalView.current DisposableEffect(view) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt new file mode 100644 index 000000000000..dae120cca981 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.composable.section + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.dimensionResource +import com.android.compose.animation.scene.SceneScope +import com.android.systemui.keyguard.ui.viewmodel.MediaCarouselViewModel +import com.android.systemui.media.controls.ui.composable.MediaCarousel +import com.android.systemui.media.controls.ui.controller.MediaCarouselController +import com.android.systemui.media.controls.ui.view.MediaHost +import com.android.systemui.media.dagger.MediaModule +import com.android.systemui.res.R +import com.android.systemui.util.animation.MeasurementInput +import javax.inject.Inject +import javax.inject.Named + +class MediaCarouselSection +@Inject +constructor( + private val mediaCarouselController: MediaCarouselController, + @param:Named(MediaModule.KEYGUARD) private val mediaHost: MediaHost, + private val mediaCarouselViewModel: MediaCarouselViewModel, +) { + + @Composable + fun SceneScope.MediaCarousel(modifier: Modifier = Modifier) { + if (!mediaCarouselViewModel.isMediaVisible) { + return + } + + if (mediaCarouselController.mediaFrame == null) { + return + } + + val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded) + // TODO(b/312714128): MediaPlayer background size is not as expected. + MediaCarousel( + modifier = + modifier.height(mediaHeight).fillMaxWidth().onSizeChanged { size -> + // Notify controller to size the carousel for the + // current space + mediaHost.measurementInput = MeasurementInput(size.width, size.height) + mediaCarouselController.setSceneContainerSize(size.width, size.height) + }, + mediaHost = mediaHost, + layoutWidth = 0, // Layout width is not used. + layoutHeight = with(LocalDensity.current) { mediaHeight.toPx() }.toInt(), + carouselController = mediaCarouselController, + ) + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt index 61b2d4e26097..d3e4553be209 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt @@ -16,9 +16,12 @@ package com.android.systemui.media.controls.ui.composable +import android.view.ViewGroup +import android.widget.FrameLayout import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.view.contains import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope import com.android.systemui.media.controls.ui.controller.MediaCarouselController @@ -45,6 +48,20 @@ fun SceneScope.MediaCarousel( AndroidView( modifier = modifier.element(MediaCarousel.Elements.Content), - factory = { _ -> carouselController.mediaFrame }, + factory = { context -> + FrameLayout(context).apply { + val mediaFrame = carouselController.mediaFrame + (mediaFrame.parent as? ViewGroup)?.removeView(mediaFrame) + addView(mediaFrame) + } + }, + update = { + if (it.contains(carouselController.mediaFrame)) { + return@AndroidView + } + val mediaFrame = carouselController.mediaFrame + (mediaFrame.parent as? ViewGroup)?.removeView(mediaFrame) + it.addView(mediaFrame) + }, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index b611e0aafa2f..36919d0c74a4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -1052,32 +1052,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true) faceAuthenticateIsCalled() } - @Test - fun authFailedCallAfterAuthLockedOutErrorShouldBeIgnored() = - testScope.runTest { - initCollectors() - allPreconditionsToRunFaceAuthAreTrue() - runCurrent() - assertThat(canFaceAuthRun()).isTrue() - - underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, false) - runCurrent() - - faceAuthenticateIsCalled() - authenticationCallback.value.onAuthenticationError( - FACE_ERROR_LOCKOUT_PERMANENT, - "Too many attempts, face not available" - ) - - val lockoutError = authStatus() as ErrorFaceAuthenticationStatus - assertThat(lockedOut()).isTrue() - assertThat(lockoutError.isLockoutError()).isTrue() - - authenticationCallback.value.onAuthenticationFailed() - runCurrent() - - assertThat(authStatus()).isEqualTo(lockoutError) - } private suspend fun TestScope.testGatingCheckForFaceAuth( gatingCheckModifier: suspend () -> Unit diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt index d4dd2ac78e2a..ad1cef1c1e92 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope +import com.android.systemui.res.R import com.android.systemui.testKosmos import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -46,6 +47,7 @@ class LockscreenContentViewModelTest : SysuiTestCase() { fun setup() { with(kosmos) { fakeFeatureFlagsClassic.set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, true) + overrideResource(R.bool.config_use_split_notification_shade, false) underTest = lockscreenContentViewModel } } @@ -87,11 +89,21 @@ class LockscreenContentViewModelTest : SysuiTestCase() { } @Test + fun areNotificationsVisible_splitShadeTrue_true() = + with(kosmos) { + testScope.runTest { + overrideResource(R.bool.config_use_split_notification_shade, true) + kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) + + assertThat(underTest.areNotificationsVisible(context.resources)).isTrue() + } + } + @Test fun areNotificationsVisible_withSmallClock_true() = with(kosmos) { testScope.runTest { kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL) - assertThat(underTest.areNotificationsVisible).isTrue() + assertThat(underTest.areNotificationsVisible(context.resources)).isTrue() } } @@ -100,7 +112,7 @@ class LockscreenContentViewModelTest : SysuiTestCase() { with(kosmos) { testScope.runTest { kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) - assertThat(underTest.areNotificationsVisible).isFalse() + assertThat(underTest.areNotificationsVisible(context.resources)).isFalse() } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index 6390e82321f0..db4d42f4c864 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -53,6 +53,7 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.FakeGlobalSettings; import com.android.systemui.util.time.FakeSystemClock; @@ -142,6 +143,12 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { } } + @Override + public void SysuiSetup() throws Exception { + super.SysuiSetup(); + mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME); + } + @Test public void testShowNotification_addsEntry() { final BaseHeadsUpManager alm = createHeadsUpManager(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java index ec23f76935d6..c032d7cb06b2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java @@ -39,6 +39,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; +import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -136,6 +137,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest { @Before public void setUp() { + mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME); + when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false)); final AccessibilityManagerWrapper accessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java index 363dd014beb6..f528ec8af134 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java @@ -18,11 +18,16 @@ package com.android.keyguard; import android.content.res.ColorStateList; import android.content.res.Configuration; +import android.hardware.biometrics.BiometricSourceType; +import android.os.SystemClock; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; +import android.util.Log; +import android.util.Pair; import android.view.View; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -40,6 +45,16 @@ import javax.inject.Inject; public class KeyguardMessageAreaController<T extends KeyguardMessageArea> extends ViewController<T> { /** + * Pair representing: + * first - BiometricSource the currently displayed message is associated with. + * second - Timestamp the biometric message came in uptimeMillis. + * This Pair can be null if the message is not associated with a biometric. + */ + @Nullable + private Pair<BiometricSourceType, Long> mMessageBiometricSource = null; + private static final Long SKIP_SHOWING_FACE_MESSAGE_AFTER_FP_MESSAGE_MS = 3500L; + + /** * Delay before speaking an accessibility announcement. Used to prevent * lift-to-type from interrupting itself. */ @@ -149,12 +164,42 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea> * Sets a message to the underlying text view. */ public void setMessage(CharSequence s, boolean animate) { + setMessage(s, animate, null); + } + + /** + * Sets a message to the underlying text view. + */ + public void setMessage(CharSequence s, BiometricSourceType biometricSourceType) { + setMessage(s, true, biometricSourceType); + } + + private void setMessage( + CharSequence s, + boolean animate, + BiometricSourceType biometricSourceType) { + final long uptimeMillis = SystemClock.uptimeMillis(); + if (skipShowingFaceMessage(biometricSourceType, uptimeMillis)) { + Log.d("KeyguardMessageAreaController", "Skip showing face message \"" + s + "\""); + return; + } + mMessageBiometricSource = new Pair<>(biometricSourceType, uptimeMillis); if (mView.isDisabled()) { return; } mView.setMessage(s, animate); } + private boolean skipShowingFaceMessage( + BiometricSourceType biometricSourceType, Long currentUptimeMillis + ) { + return mMessageBiometricSource != null + && biometricSourceType == BiometricSourceType.FACE + && mMessageBiometricSource.first == BiometricSourceType.FINGERPRINT + && (currentUptimeMillis - mMessageBiometricSource.second) + < SKIP_SHOWING_FACE_MESSAGE_AFTER_FP_MESSAGE_MS; + } + public void setMessage(int resId) { String message = resId != 0 ? mView.getResources().getString(resId) : null; setMessage(message); diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt index d2ad096c1207..ce4032aaea05 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt @@ -16,6 +16,7 @@ package com.android.keyguard.logging +import android.hardware.biometrics.BiometricSourceType import com.android.systemui.biometrics.AuthRippleController import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController import com.android.systemui.log.LogBuffer @@ -117,6 +118,26 @@ constructor( ) } + fun logDropNonFingerprintMessage( + message: CharSequence, + followUpMessage: CharSequence?, + biometricSourceType: BiometricSourceType?, + ) { + buffer.log( + KeyguardIndicationController.TAG, + LogLevel.DEBUG, + { + str1 = message.toString() + str2 = followUpMessage?.toString() + str3 = biometricSourceType?.name + }, + { + "droppingNonFingerprintMessage message=$str1 " + + "followUpMessage:$str2 biometricSourceType:$str3" + } + ) + } + fun logUpdateBatteryIndication( powerIndication: String, pluggedIn: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 0bd44f0f3901..f4cd5b963e41 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -17,7 +17,6 @@ package com.android.systemui.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; -import static android.hardware.biometrics.Flags.customBiometricPrompt; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION; @@ -33,6 +32,7 @@ import android.graphics.PixelFormat; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager.Authenticators; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.PromptInfo; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -402,7 +402,12 @@ public class AuthContainerView extends LinearLayout @Nullable FaceSensorPropertiesInternal faceProps, @NonNull VibratorHelper vibratorHelper ) { - if (Utils.isBiometricAllowed(config.mPromptInfo) || customBiometricPrompt()) { + // Set this value before showing either of the prompt. + mPromptSelectorInteractorProvider.get().setShouldShowBpWithoutIconForCredential( + config.mPromptInfo); + + if (Utils.isBiometricAllowed(config.mPromptInfo) + || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) { addBiometricView(config, layoutInflater, viewModel, fpProps, faceProps, vibratorHelper); } else if (constraintBp() && Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) { addCredentialView(true, false); @@ -411,7 +416,6 @@ public class AuthContainerView extends LinearLayout } } - private void addBiometricView(@NonNull Config config, @NonNull LayoutInflater layoutInflater, @NonNull PromptViewModel viewModel, @Nullable FingerprintSensorPropertiesInternal fpProps, @@ -534,7 +538,8 @@ public class AuthContainerView extends LinearLayout () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED)); if (constraintBp()) { // Do nothing on attachment with constraintLayout - } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo) || customBiometricPrompt()) { + } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo) + || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) { mBiometricScrollView.addView(mBiometricView.asView()); } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) { addCredentialView(true /* animatePanel */, false /* animateContents */); @@ -819,6 +824,9 @@ public class AuthContainerView extends LinearLayout final Runnable endActionRunnable = () -> { setVisibility(View.INVISIBLE); + if (Flags.customBiometricPrompt()) { + mPromptSelectorInteractorProvider.get().resetPrompt(); + } removeWindowIfAttached(); }; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt index ad7bb0e61178..b87fadf995e6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt @@ -16,8 +16,11 @@ package com.android.systemui.biometrics.data.repository +import android.hardware.biometrics.Flags import android.hardware.biometrics.PromptInfo import com.android.systemui.biometrics.AuthController +import com.android.systemui.biometrics.Utils +import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow @@ -65,6 +68,18 @@ interface PromptRepository { */ val isConfirmationRequired: Flow<Boolean> + /** + * If biometric prompt without icon needs to show for displaying content prior to credential + * view. + */ + val showBpWithoutIconForCredential: StateFlow<Boolean> + + /** + * Update whether biometric prompt without icon needs to show for displaying content prior to + * credential view, which should be set before [setPrompt]. + */ + fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) + /** Update the prompt configuration, which should be set before [isShowing]. */ fun setPrompt( promptInfo: PromptInfo, @@ -129,6 +144,19 @@ constructor( } .distinctUntilChanged() + private val _showBpWithoutIconForCredential: MutableStateFlow<Boolean> = MutableStateFlow(false) + override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow() + + override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) { + val hasCredentialViewShown = kind.value !is PromptKind.Biometric + val showBpForCredential = + Flags.customBiometricPrompt() && + !Utils.isBiometricAllowed(promptInfo) && + isDeviceCredentialAllowed(promptInfo) && + promptInfo.contentView != null + _showBpWithoutIconForCredential.value = showBpForCredential && !hasCredentialViewShown + } + override fun setPrompt( promptInfo: PromptInfo, userId: Int, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt index e3facff9af12..94cea5702fe3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt @@ -30,6 +30,7 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -59,6 +60,13 @@ constructor( /** If the prompt is currently showing. */ val isShowing: Flow<Boolean> = biometricPromptRepository.isShowing + /** + * If biometric prompt without icon needs to show for displaying content prior to credential + * view. + */ + val showBpWithoutIconForCredential: StateFlow<Boolean> = + biometricPromptRepository.showBpWithoutIconForCredential + /** Metadata about the current credential prompt, including app-supplied preferences. */ val prompt: Flow<BiometricPromptRequest.Credential?> = combine( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt index b3f95748ccdc..45816c12281e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt @@ -32,6 +32,7 @@ import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map @@ -70,6 +71,18 @@ interface PromptSelectorInteractor { /** Fingerprint sensor type */ val sensorType: Flow<FingerprintSensorType> + /** + * If biometric prompt without icon needs to show for displaying content prior to credential + * view. + */ + val showBpWithoutIconForCredential: StateFlow<Boolean> + + /** + * Update whether biometric prompt without icon needs to show for displaying content prior to + * credential view, which should be set before [PromptRepository.setPrompt]. + */ + fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) + /** Use biometrics for authentication. */ fun useBiometricsForAuthentication( promptInfo: PromptInfo, @@ -154,6 +167,12 @@ constructor( override val sensorType: Flow<FingerprintSensorType> = fingerprintPropertyRepository.sensorType + override val showBpWithoutIconForCredential = promptRepository.showBpWithoutIconForCredential + + override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) { + promptRepository.setShouldShowBpWithoutIconForCredential(promptInfo) + } + override fun useBiometricsForAuthentication( promptInfo: PromptInfo, userId: Int, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt index 6133a51c6497..6f079e2d3b3a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt @@ -17,6 +17,7 @@ sealed class BiometricPromptRequest( val title: String, val subtitle: String, val description: String, + val contentView: PromptContentView?, val userInfo: BiometricUserInfo, val operationInfo: BiometricOperationInfo, val showEmergencyCallButton: Boolean, @@ -33,11 +34,11 @@ sealed class BiometricPromptRequest( title = info.title?.toString() ?: "", subtitle = info.subtitle?.toString() ?: "", description = info.description?.toString() ?: "", + contentView = info.contentView, userInfo = userInfo, operationInfo = operationInfo, showEmergencyCallButton = info.isShowEmergencyCallButton ) { - val contentView: PromptContentView? = info.contentView val logoRes: Int = info.logoRes val logoBitmap: Bitmap? = info.logoBitmap val logoDescription: String? = info.logoDescription @@ -54,6 +55,7 @@ sealed class BiometricPromptRequest( title = (info.deviceCredentialTitle ?: info.title)?.toString() ?: "", subtitle = (info.deviceCredentialSubtitle ?: info.subtitle)?.toString() ?: "", description = (info.deviceCredentialDescription ?: info.description)?.toString() ?: "", + contentView = info.contentView, userInfo = userInfo, operationInfo = operationInfo, showEmergencyCallButton = info.isShowEmergencyCallButton diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index cd5b12482d83..ea3924732d41 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -22,7 +22,6 @@ import android.content.Context import android.hardware.biometrics.BiometricAuthenticator import android.hardware.biometrics.BiometricConstants import android.hardware.biometrics.BiometricPrompt -import android.hardware.biometrics.Flags.customBiometricPrompt import android.hardware.face.FaceManager import android.text.method.ScrollingMovementMethod import android.util.Log @@ -151,17 +150,6 @@ object BiometricViewBinder { // these do not change and need to be set before any size transitions val modalities = viewModel.modalities.first() - // If there is no biometrics available, biometric prompt is showing just for displaying - // content, no authentication needed. - if (!(customBiometricPrompt() && modalities.isEmpty)) { - PromptIconViewBinder.bind( - iconView, - iconOverlayView, - iconSizeOverride, - viewModel, - ) - } - if (modalities.hasFingerprint) { /** * Load the given [rawResources] immediately so they are cached for use in the @@ -233,6 +221,19 @@ object BiometricViewBinder { ) } + lifecycleScope.launch { + viewModel.showBpWithoutIconForCredential.collect { + if (!it) { + PromptIconViewBinder.bind( + iconView, + iconOverlayView, + iconSizeOverride, + viewModel, + ) + } + } + } + // TODO(b/251476085): migrate legacy icon controllers and remove // The fingerprint sensor is started by the legacy // AuthContainerView#onDialogAnimatedIn in all cases but the implicit coex flow diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index a37d9168dfd3..8336d5e5ae28 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -21,7 +21,6 @@ import android.animation.AnimatorSet import android.animation.ValueAnimator import android.graphics.Outline import android.graphics.Rect -import android.hardware.biometrics.Flags import android.transition.AutoTransition import android.transition.TransitionManager import android.view.Surface @@ -55,6 +54,7 @@ import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall import com.android.systemui.biometrics.ui.viewmodel.isRight import com.android.systemui.biometrics.ui.viewmodel.isSmall import com.android.systemui.biometrics.ui.viewmodel.isTop +import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import kotlin.math.abs @@ -111,14 +111,9 @@ object BiometricViewSizeBinder { val smallConstraintSet = ConstraintSet() smallConstraintSet.clone(mediumConstraintSet) - viewsToHideWhenSmall.forEach { smallConstraintSet.setVisibility(it.id, View.GONE) } val largeConstraintSet = ConstraintSet() largeConstraintSet.clone(mediumConstraintSet) - viewsToHideWhenSmall.forEach { largeConstraintSet.setVisibility(it.id, View.GONE) } - largeConstraintSet.setVisibility(iconHolderView.id, View.GONE) - largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) - largeConstraintSet.setVisibility(R.id.indicator, View.GONE) largeConstraintSet.setGuidelineBegin(leftGuideline.id, 0) largeConstraintSet.setGuidelineEnd(rightGuideline.id, 0) largeConstraintSet.setGuidelineEnd(bottomGuideline.id, 0) @@ -219,13 +214,18 @@ object BiometricViewSizeBinder { } } - view.repeatWhenAttached { - var currentSize: PromptSize? = null - val modalities = viewModel.modalities.first() - // TODO(b/288175072): Move all visibility settings together. - // If there is no biometrics available, biometric prompt is showing just for - // displaying content, no authentication needed. - if (Flags.customBiometricPrompt() && modalities.isEmpty) { + fun setConstraintSetVisibility() { + viewsToHideWhenSmall.forEach { + mediumConstraintSet.setVisibility(it.id, it.showContentOrHide()) + largeConstraintSet.setVisibility(it.id, View.GONE) + smallConstraintSet.setVisibility(it.id, View.GONE) + } + + largeConstraintSet.setVisibility(iconHolderView.id, View.GONE) + largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) + largeConstraintSet.setVisibility(R.id.indicator, View.GONE) + + if (viewModel.showBpWithoutIconForCredential.value) { smallConstraintSet.setVisibility(iconHolderView.id, View.GONE) smallConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) smallConstraintSet.setVisibility(R.id.indicator, View.GONE) @@ -233,12 +233,17 @@ object BiometricViewSizeBinder { mediumConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) mediumConstraintSet.setVisibility(R.id.indicator, View.GONE) } + } + + view.repeatWhenAttached { + var currentSize: PromptSize? = null + lifecycleScope.launch { combine(viewModel.position, viewModel.size, ::Pair).collect { (position, size) -> view.doOnAttach { measureBounds(position) - + setConstraintSetVisibility() when { size.isSmall -> { val ratio = @@ -313,7 +318,6 @@ object BiometricViewSizeBinder { // TODO(b/251476085): migrate the legacy panel controller and simplify this view.repeatWhenAttached { var currentSize: PromptSize? = null - val modalities = viewModel.modalities.first() lifecycleScope.launch { /** * View is only set visible in BiometricViewSizeBinder once PromptSize is @@ -331,11 +335,13 @@ object BiometricViewSizeBinder { // prepare for animated size transitions for (v in viewsToHideWhenSmall) { - v.showContentOrHide(forceHide = size.isSmall) + v.visibility = v.showContentOrHide(forceHide = size.isSmall) } - if (Flags.customBiometricPrompt() && modalities.isEmpty) { + + if (viewModel.showBpWithoutIconForCredential.value) { iconHolderView.visibility = View.GONE } + if (currentSize == null && size.isSmall) { iconHolderView.alpha = 0f } @@ -344,9 +350,9 @@ object BiometricViewSizeBinder { } // TODO(b/302735104): Fix wrong height due to the delay of - // PromptContentView. addOnLayoutChangeListener() will cause crash when - // showing credential view, since |PromptIconViewModel| won't release - // the flow. + // PromptContentView. addOnLayoutChangeListener() will cause crash + // when showing credential view, since |PromptIconViewModel| won't + // release the flow. // propagate size changes to legacy panel controller and animate // transitions view.doOnLayout { @@ -460,15 +466,14 @@ private fun View.isLandscape(): Boolean { return r == Surface.ROTATION_90 || r == Surface.ROTATION_270 } -private fun View.showContentOrHide(forceHide: Boolean = false) { +private fun View.showContentOrHide(forceHide: Boolean = false): Int { val isTextViewWithBlankText = this is TextView && this.text.isBlank() val isImageViewWithoutImage = this is ImageView && this.drawable == null - visibility = - if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) { - View.GONE - } else { - View.VISIBLE - } + return if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) { + View.GONE + } else { + View.VISIBLE + } } private fun View.centerX(): Int { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt index 03c5c5354ad7..46be8c74cee3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt @@ -4,13 +4,13 @@ import android.content.Context import android.graphics.drawable.Drawable import android.text.InputType import com.android.internal.widget.LockPatternView -import com.android.systemui.res.R import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.domain.interactor.CredentialStatus import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor import com.android.systemui.biometrics.domain.model.BiometricPromptRequest import com.android.systemui.biometrics.shared.model.BiometricUserInfo import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.res.R import javax.inject.Inject import kotlin.reflect.KClass import kotlinx.coroutines.flow.Flow @@ -32,14 +32,16 @@ constructor( /** Top level information about the prompt. */ val header: Flow<CredentialHeaderViewModel> = - credentialInteractor.prompt.filterIsInstance<BiometricPromptRequest.Credential>().map { - request -> + combine( + credentialInteractor.prompt.filterIsInstance<BiometricPromptRequest.Credential>(), + credentialInteractor.showBpWithoutIconForCredential + ) { request, showBpWithoutIconForCredential -> BiometricPromptHeaderViewModelImpl( request, user = request.userInfo, title = request.title, - subtitle = request.subtitle, - description = request.description, + subtitle = if (showBpWithoutIconForCredential) "" else request.subtitle, + description = if (showBpWithoutIconForCredential) "" else request.description, icon = applicationContext.asLockIcon(request.userInfo.deviceCredentialOwnerId), showEmergencyCallButton = request.showEmergencyCallButton ) @@ -145,7 +147,7 @@ constructor( .createLaunchEmergencyDialerIntent(null) .setFlags( android.content.Intent.FLAG_ACTIVITY_NEW_TASK or - android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP + android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP ) context.startActivity(intent) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index c933e0e31d40..2c749baadb9c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -47,6 +47,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine @@ -123,6 +124,9 @@ constructor( /** The kind of credential the user has. */ val credentialKind: Flow<PromptKind> = promptSelectorInteractor.credentialKind + val showBpWithoutIconForCredential: StateFlow<Boolean> = + promptSelectorInteractor.showBpWithoutIconForCredential + /** The label to use for the cancel button. */ val negativeButtonText: Flow<String> = promptSelectorInteractor.prompt.map { it?.negativeButtonText ?: "" } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt new file mode 100644 index 000000000000..e789475b7877 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 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.bouncer.shared.flag + +import com.android.systemui.Flags +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.scene.shared.flag.SceneContainerFlags +import dagger.Module +import dagger.Provides + +interface ComposeBouncerFlags { + + /** + * Returns `true` if the Compose bouncer is enabled or if the scene container framework is + * enabled; `false` otherwise. + */ + fun isComposeBouncerOrSceneContainerEnabled(): Boolean + + /** + * Returns `true` if only compose bouncer is enabled and scene container framework is not + * enabled. + */ + @Deprecated( + "Avoid using this, this is meant to be used only by the glue code " + + "that includes compose bouncer in legacy keyguard.", + replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()") + ) + fun isOnlyComposeBouncerEnabled(): Boolean +} + +class ComposeBouncerFlagsImpl(private val sceneContainerFlags: SceneContainerFlags) : + ComposeBouncerFlags { + + override fun isComposeBouncerOrSceneContainerEnabled(): Boolean { + return sceneContainerFlags.isEnabled() || Flags.composeBouncer() + } + + @Deprecated( + "Avoid using this, this is meant to be used only by the glue code " + + "that includes compose bouncer in legacy keyguard.", + replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()") + ) + override fun isOnlyComposeBouncerEnabled(): Boolean { + return !sceneContainerFlags.isEnabled() && Flags.composeBouncer() + } +} + +@Module +object ComposeBouncerFlagsModule { + @Provides + @SysUISingleton + fun impl(sceneContainerFlags: SceneContainerFlags): ComposeBouncerFlags { + return ComposeBouncerFlagsImpl(sceneContainerFlags) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt index dd253a8f6eff..36d3ed52b655 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt @@ -4,10 +4,10 @@ import android.view.ViewGroup import com.android.keyguard.KeyguardMessageAreaController import com.android.keyguard.ViewMediatorCallback import com.android.keyguard.dagger.KeyguardBouncerComponent -import com.android.systemui.Flags import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags import com.android.systemui.bouncer.ui.BouncerDialogFactory import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel @@ -55,12 +55,15 @@ constructor( class BouncerViewBinder @Inject constructor( + private val composeBouncerFlags: ComposeBouncerFlags, private val legacyBouncerDependencies: Lazy<LegacyBouncerDependencies>, private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>, ) { fun bind(view: ViewGroup) { if ( - ComposeFacade.isComposeAvailable() && Flags.composeBouncer() && COMPOSE_BOUNCER_ENABLED + COMPOSE_BOUNCER_ENABLED && + composeBouncerFlags.isOnlyComposeBouncerEnabled() && + ComposeFacade.isComposeAvailable() ) { val deps = composeBouncerDependencies.get() ComposeBouncerViewBinder.bind( diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 4466cbbe05be..62875783ef5f 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -28,6 +28,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationWipeModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor +import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text @@ -35,7 +36,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.user.ui.viewmodel.UserActionViewModel import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel @@ -72,7 +72,7 @@ class BouncerViewModel( private val simBouncerInteractor: SimBouncerInteractor, private val authenticationInteractor: AuthenticationInteractor, private val selectedUserInteractor: SelectedUserInteractor, - flags: SceneContainerFlags, + flags: ComposeBouncerFlags, selectedUser: Flow<UserViewModel>, users: Flow<List<UserViewModel>>, userSwitcherMenu: Flow<List<UserActionViewModel>>, @@ -233,7 +233,7 @@ class BouncerViewModel( private var lockoutCountdownJob: Job? = null init { - if (flags.isEnabled()) { + if (flags.isComposeBouncerOrSceneContainerEnabled()) { // Keeps the lockout dialog up-to-date. applicationScope.launch { bouncerInteractor.onLockoutStarted.collect { @@ -478,7 +478,7 @@ object BouncerViewModelModule { actionButtonInteractor: BouncerActionButtonInteractor, authenticationInteractor: AuthenticationInteractor, selectedUserInteractor: SelectedUserInteractor, - flags: SceneContainerFlags, + flags: ComposeBouncerFlags, userSwitcherViewModel: UserSwitcherViewModel, clock: SystemClock, devicePolicyManager: DevicePolicyManager, diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt index 82d943796e2a..a02c5ce8bb14 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -23,6 +23,8 @@ import com.android.systemui.communal.data.repository.CommunalRepositoryModule import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule +import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel +import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModelImpl import com.android.systemui.communal.widgets.CommunalWidgetModule import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl @@ -47,4 +49,9 @@ interface CommunalModule { fun bindEditWidgetsActivityStarter( starter: EditWidgetsActivityStarterImpl ): EditWidgetsActivityStarter + + @Binds + fun bindCommunalTransitionViewModel( + impl: CommunalTransitionViewModelImpl + ): CommunalTransitionViewModel } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt new file mode 100644 index 000000000000..eed0aa397b65 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 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.communal.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.merge + +/** View model for transitions related to the communal hub. */ +interface CommunalTransitionViewModel { + val isUmoOnCommunal: Flow<Boolean> +} + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class CommunalTransitionViewModelImpl +@Inject +constructor( + glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, + lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel, + dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel, + glanceableHubToDreamTransitionViewModel: GlanceableHubToDreamingTransitionViewModel, +) : CommunalTransitionViewModel { + /** + * Whether UMO location should be on communal. This flow is responsive to transitions so that a + * new value is emitted at the right step of a transition to/from communal hub that the location + * of UMO should be updated. + */ + override val isUmoOnCommunal: Flow<Boolean> = + merge( + lockscreenToGlanceableHubTransitionViewModel.showUmo, + glanceableHubToLockscreenTransitionViewModel.showUmo, + dreamToGlanceableHubTransitionViewModel.showUmo, + glanceableHubToDreamTransitionViewModel.showUmo, + ) + .distinctUntilChanged() +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 1157d97f2f2e..19af371d1dfa 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -51,6 +51,7 @@ import com.android.systemui.complication.dagger.ComplicationComponent; import com.android.systemui.controls.dagger.ControlsModule; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.SystemUser; +import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.demomode.dagger.DemoModeModule; import com.android.systemui.deviceentry.DeviceEntryModule; import com.android.systemui.display.DisplayModule; @@ -365,7 +366,8 @@ public abstract class SystemUIModule { SysUiState sysUiState, FeatureFlags featureFlags, NotifPipelineFlags notifPipelineFlags, - @Main Executor sysuiMainExecutor) { + @Main Executor sysuiMainExecutor, + @UiBackground Executor sysuiUiBgExecutor) { return Optional.ofNullable(BubblesManager.create(context, bubblesOptional, notificationShadeWindowController, @@ -384,7 +386,8 @@ public abstract class SystemUIModule { sysUiState, featureFlags, notifPipelineFlags, - sysuiMainExecutor)); + sysuiMainExecutor, + sysuiUiBgExecutor)); } @Binds diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt index cf7d60140aee..baae986c494d 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt @@ -62,6 +62,10 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.UserRepository import com.google.errorprone.annotations.CompileTimeConstant +import java.io.PrintWriter +import java.util.Arrays +import java.util.stream.Collectors +import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -83,10 +87,6 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.io.PrintWriter -import java.util.Arrays -import java.util.stream.Collectors -import javax.inject.Inject /** * API to run face authentication and detection for device entry / on keyguard (as opposed to the @@ -431,11 +431,11 @@ constructor( override fun onAuthenticationFailed() { _isAuthenticated.value = false faceAuthLogger.authenticationFailed() + _authenticationStatus.value = FailedFaceAuthenticationStatus() if (!_isLockedOut.value) { // onAuthenticationError gets invoked before onAuthenticationFailed when the // last auth attempt locks out face authentication. - // Skip updating the authentication status in such a scenario. - _authenticationStatus.value = FailedFaceAuthenticationStatus() + // Skip onFaceAuthRequestCompleted in such a scenario. onFaceAuthRequestCompleted() } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt index 6bfe8d91b5fc..846013cef326 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt @@ -21,9 +21,12 @@ import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInte import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus +import com.android.systemui.deviceentry.shared.model.FaceFailureMessage +import com.android.systemui.deviceentry.shared.model.FaceLockoutMessage import com.android.systemui.deviceentry.shared.model.FaceMessage import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus +import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage import com.android.systemui.deviceentry.shared.model.FingerprintMessage import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus @@ -97,11 +100,7 @@ constructor( fingerprintAuthInteractor.fingerprintHelp .sample(biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair) .filter { (_, fingerprintAuthAllowed) -> fingerprintAuthAllowed } - .map { (helpStatus, _) -> - FingerprintMessage( - helpStatus.msg, - ) - } + .map { (helpStatus, _) -> FingerprintMessage(helpStatus.msg) } private val fingerprintFailMessage: Flow<FingerprintMessage> = fingerprintPropertyInteractor.isUdfps.flatMapLatest { isUdfps -> @@ -109,7 +108,7 @@ constructor( .sample(biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed) .filter { fingerprintAuthAllowed -> fingerprintAuthAllowed } .map { - FingerprintMessage( + FingerprintFailureMessage( if (isUdfps) { resources.getString( com.android.internal.R.string.fingerprint_udfps_error_not_match @@ -118,7 +117,7 @@ constructor( resources.getString( com.android.internal.R.string.fingerprint_error_not_match ) - }, + } ) } } @@ -154,7 +153,7 @@ constructor( faceFailure .sample(biometricSettingsInteractor.faceAuthCurrentlyAllowed) .filter { faceAuthCurrentlyAllowed -> faceAuthCurrentlyAllowed } - .map { FaceMessage(resources.getString(R.string.keyguard_face_failed)) } + .map { FaceFailureMessage(resources.getString(R.string.keyguard_face_failed)) } private val faceErrorMessage: Flow<FaceMessage> = faceError @@ -173,6 +172,7 @@ constructor( FaceTimeoutMessage(status.msg) } } + status.isLockoutError() -> FaceLockoutMessage(status.msg) else -> FaceMessage(status.msg) } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt index 59c3f7f8aded..2ced8c41713f 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt @@ -32,9 +32,15 @@ data class FaceTimeoutMessage( private val faceTimeoutMessage: String?, ) : FaceMessage(faceTimeoutMessage) +data class FaceLockoutMessage(private val msg: String?) : FaceMessage(msg) + +data class FaceFailureMessage(private val msg: String) : FaceMessage(msg) + /** Fingerprint biometric message */ open class FingerprintMessage(fingerprintMessage: String?) : BiometricMessage(fingerprintMessage) data class FingerprintLockoutMessage( private val fingerprintLockoutMessage: String?, ) : FingerprintMessage(fingerprintLockoutMessage) + +data class FingerprintFailureMessage(private val msg: String?) : FingerprintMessage(msg) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 7263ae96b3a8..cb1571e7d702 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -44,6 +44,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch @@ -287,7 +288,7 @@ constructor( if (KeyguardWmStateRefactor.isEnabled) { // When the refactor is enabled, we no longer use isKeyguardGoingAway. scope.launch { - swipeToDismissInteractor.dismissFling.collect { _ -> + swipeToDismissInteractor.dismissFling.filterNotNull().collect { _ -> startTransitionTo(KeyguardState.GONE) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 719edd7e8535..37b331cd8455 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -35,6 +35,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.OFF import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -46,6 +47,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn /** Encapsulates business-logic related to the keyguard transitions. */ @OptIn(ExperimentalCoroutinesApi::class) @@ -206,6 +208,21 @@ constructor( .shareIn(scope, SharingStarted.Eagerly, replay = 1) /** + * A pair of the most recent STARTED step, and the transition step immediately preceding it. The + * transition framework enforces that the previous step is either a CANCELED or FINISHED step, + * and that the previous step was *to* the state the STARTED step is *from*. + * + * This flow can be used to access the previous step to determine whether it was CANCELED or + * FINISHED. In the case of a CANCELED step, we can also figure out which state we were coming + * from when we were canceled. + */ + val startedStepWithPrecedingStep = + transitions + .pairwise() + .filter { it.newValue.transitionState == TransitionState.STARTED } + .stateIn(scope, SharingStarted.Eagerly, null) + + /** * The last [KeyguardState] to which we [TransitionState.FINISHED] a transition. * * WARNING: This will NOT emit a value if a transition is CANCELED, and will also not emit a diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt index b81793ecec64..cff74b333530 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -121,19 +123,27 @@ constructor( * want to know if the AOD/clock/notifs/etc. are visible. */ val lockscreenVisibility: Flow<Boolean> = - combine( - transitionInteractor.startedKeyguardTransitionStep, - transitionInteractor.finishedKeyguardState, - ) { startedStep, finishedState -> - // If we finished the transition, use the finished state. If we're running a - // transition, use the state we're transitioning FROM. This can be different from - // the last finished state if a transition is interrupted. For example, if we were - // transitioning from GONE to AOD and then started AOD -> LOCKSCREEN mid-transition, - // we want to immediately use the visibility for AOD (lockscreenVisibility=true) - // even though the lastFinishedState is still GONE (lockscreenVisibility=false). - if (finishedState == startedStep.to) finishedState else startedStep.from + transitionInteractor.currentKeyguardState + .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair) + .map { (currentState, startedWithPrev) -> + val startedFromStep = startedWithPrev?.previousValue + val startedStep = startedWithPrev?.newValue + val returningToGoneAfterCancellation = + startedStep?.to == KeyguardState.GONE && + startedFromStep?.transitionState == TransitionState.CANCELED && + startedFromStep.from == KeyguardState.GONE + + if (!returningToGoneAfterCancellation) { + // By default, apply the lockscreen visibility of the current state. + KeyguardState.lockscreenVisibleInState(currentState) + } else { + // If we're transitioning to GONE after a prior canceled transition from GONE, + // then this is the camera launch transition from an asleep state back to GONE. + // We don't want to show the lockscreen since we're aborting the lock and going + // back to GONE. + KeyguardState.lockscreenVisibleInState(KeyguardState.GONE) + } } - .map(KeyguardState::lockscreenVisibleInState) .distinctUntilChanged() /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt index c64f277b519a..5cb2ec9642b9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt @@ -28,6 +28,7 @@ import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton @@ -65,6 +66,15 @@ constructor( name = "DREAMING->GLANCEABLE_HUB: dreamOverlayAlpha", ) + // Show UMO once the transition starts. + val showUmo: Flow<Boolean> = + transitionAnimation + .sharedFlow( + duration = TO_GLANCEABLE_HUB_DURATION, + onStep = { it }, + ) + .map { step -> step != 0f } + private companion object { val TO_GLANCEABLE_HUB_DURATION = 1.seconds } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt index 478c4faa1be3..90be4f904105 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt @@ -28,6 +28,7 @@ import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton @@ -66,6 +67,15 @@ constructor( ) } + // Show UMO until transition finishes. + val showUmo: Flow<Boolean> = + transitionAnimation + .sharedFlow( + duration = FROM_GLANCEABLE_HUB_DURATION, + onStep = { it }, + ) + .map { step -> step != 1f } + private companion object { val FROM_GLANCEABLE_HUB_DURATION = 1.seconds } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt index e5b596419efe..f81598f717b0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt @@ -64,6 +64,9 @@ constructor( ) .onStart { emit(0f) } + // Show UMO as long as keyguard is not visible. + val showUmo: Flow<Boolean> = keyguardAlpha.map { alpha -> alpha == 0f } + val keyguardTranslationX: Flow<StateToValue> = configurationInteractor .dimensionPixelSize(R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt index d79288947e78..23320be129fd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.res.R +import com.android.systemui.statusbar.policy.SplitShadeStateController import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted @@ -39,6 +40,7 @@ constructor( private val interactor: KeyguardBlueprintInteractor, private val authController: AuthController, val longPress: KeyguardLongPressViewModel, + val splitShadeStateController: SplitShadeStateController, ) { private val clockSize = clockInteractor.clockSize @@ -46,8 +48,10 @@ constructor( get() = authController.isUdfpsSupported val isLargeClockVisible: Boolean get() = clockSize.value == KeyguardClockSwitch.LARGE - val areNotificationsVisible: Boolean - get() = !isLargeClockVisible + fun areNotificationsVisible(resources: Resources): Boolean { + return !isLargeClockVisible || + splitShadeStateController.shouldUseSplitNotificationShade(resources) + } fun getSmartSpacePaddingTop(resources: Resources): Int { return if (isLargeClockVisible) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt index 978e71e2a825..0c33e18d0113 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt @@ -63,6 +63,9 @@ constructor( ) .onStart { emit(1f) } + // Show UMO as long as keyguard is not visible. + val showUmo: Flow<Boolean> = keyguardAlpha.map { alpha -> alpha == 0f } + val keyguardTranslationX: Flow<StateToValue> = configurationInteractor .dimensionPixelSize(R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt new file mode 100644 index 000000000000..027a739b4abf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import javax.inject.Inject + +class MediaCarouselViewModel @Inject constructor(mediaDataManager: MediaDataManager) { + val isMediaVisible: Boolean = mediaDataManager.hasActiveMediaOrRecommendation() +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt index dbd71f3e3a04..a4f3e2174791 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt @@ -36,7 +36,7 @@ import androidx.annotation.VisibleForTesting import com.android.app.animation.Interpolators import com.android.app.tracing.traceSection import com.android.keyguard.KeyguardViewController -import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main @@ -58,7 +58,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.animation.UniqueObjectHostView -import com.android.systemui.util.kotlin.BooleanFlowOperators.and import com.android.systemui.util.settings.SecureSettings import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -102,7 +101,7 @@ constructor( private val mediaManager: MediaDataManager, private val keyguardViewController: KeyguardViewController, private val dreamOverlayStateController: DreamOverlayStateController, - private val communalInteractor: CommunalInteractor, + communalTransitionViewModel: CommunalTransitionViewModel, configurationController: ConfigurationController, wakefulnessLifecycle: WakefulnessLifecycle, shadeInteractor: ShadeInteractor, @@ -587,11 +586,10 @@ constructor( // Listen to the communal UI state. Make sure that communal UI is showing and hub itself is // available, ie. not disabled and able to be shown. coroutineScope.launch { - and(communalInteractor.isCommunalShowing, communalInteractor.isCommunalAvailable) - .collect { value -> - isCommunalShowing = value - updateDesiredLocation() - } + communalTransitionViewModel.isUmoOnCommunal.collect { value -> + isCommunalShowing = value + updateDesiredLocation(forceNoAnimation = true) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt new file mode 100644 index 000000000000..2850b4bb2358 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 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.media.controls.util + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the media_controls_refactor flag state. */ +@Suppress("NOTHING_TO_INLINE") +object MediaControlsRefactorFlag { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_MEDIA_CONTROLS_REFACTOR + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the flag enabled? */ + @JvmStatic + inline val isEnabled + get() = Flags.mediaControlsRefactor() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index 15747b9eb515..d4bd6daedfab 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -58,4 +58,7 @@ constructor( /** Check whether to use scene framework */ fun isSceneContainerEnabled() = sceneContainerFlags.isEnabled() && MediaInSceneContainerFlag.isEnabled + + /** Check whether to use media refactor code */ + fun isMediaControlsRefactorEnabled() = MediaControlsRefactorFlag.isEnabled } diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index c7d3a4af24c9..7d2468b2f016 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -17,6 +17,7 @@ package com.android.systemui.scene import com.android.systemui.CoreStartable +import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlagsModule import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule @@ -34,6 +35,7 @@ import dagger.multibindings.IntoMap [ BouncerSceneModule::class, CommunalSceneModule::class, + ComposeBouncerFlagsModule::class, EmptySceneModule::class, GoneSceneModule::class, LockscreenSceneModule::class, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 19fe60a60bf5..1ec86aea49d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -71,6 +71,7 @@ import android.os.UserManager; import android.provider.DeviceConfig; import android.text.TextUtils; import android.text.format.Formatter; +import android.util.Pair; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; @@ -187,6 +188,7 @@ public class KeyguardIndicationController { private CharSequence mTransientIndication; private CharSequence mBiometricMessage; private CharSequence mBiometricMessageFollowUp; + private BiometricSourceType mBiometricMessageSource; protected ColorStateList mInitialTextColorState; private boolean mVisible; private boolean mOrganizationOwnedDevice; @@ -206,7 +208,7 @@ public class KeyguardIndicationController { private int mBatteryLevel; private boolean mBatteryPresent = true; private long mChargingTimeRemaining; - private String mBiometricErrorMessageToShowOnScreenOn; + private Pair<String, BiometricSourceType> mBiometricErrorMessageToShowOnScreenOn; private final Set<Integer> mCoExFaceAcquisitionMsgIdsToShow; private final FaceHelpMessageDeferral mFaceAcquiredMessageDeferral; private boolean mInited; @@ -225,15 +227,18 @@ public class KeyguardIndicationController { mIsActiveDreamLockscreenHosted = isLockscreenHosted; updateDeviceEntryIndication(false); }; - private final ScreenLifecycle.Observer mScreenObserver = - new ScreenLifecycle.Observer() { + private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override public void onScreenTurnedOn() { mHandler.removeMessages(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON); if (mBiometricErrorMessageToShowOnScreenOn != null) { String followUpMessage = mFaceLockedOutThisAuthSession ? faceLockedOutFollowupMessage() : null; - showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn, followUpMessage); + showBiometricMessage( + mBiometricErrorMessageToShowOnScreenOn.first, + followUpMessage, + mBiometricErrorMessageToShowOnScreenOn.second + ); // We want to keep this message around in case the screen was off hideBiometricMessageDelayed(DEFAULT_HIDE_DELAY_MS); mBiometricErrorMessageToShowOnScreenOn = null; @@ -879,8 +884,35 @@ public class KeyguardIndicationController { updateTransient(); } - private void showBiometricMessage(CharSequence biometricMessage) { - showBiometricMessage(biometricMessage, null); + private void showSuccessBiometricMessage( + CharSequence biometricMessage, + @Nullable CharSequence biometricMessageFollowUp, + BiometricSourceType biometricSourceType + ) { + showBiometricMessage(biometricMessage, biometricMessageFollowUp, biometricSourceType, true); + } + + private void showSuccessBiometricMessage(CharSequence biometricMessage, + BiometricSourceType biometricSourceType) { + showSuccessBiometricMessage(biometricMessage, null, biometricSourceType); + } + + private void showBiometricMessage(CharSequence biometricMessage, + BiometricSourceType biometricSourceType) { + showBiometricMessage(biometricMessage, null, biometricSourceType, false); + } + + private void showBiometricMessage( + CharSequence biometricMessage, + @Nullable CharSequence biometricMessageFollowUp, + BiometricSourceType biometricSourceType + ) { + showBiometricMessage( + biometricMessage, + biometricMessageFollowUp, + biometricSourceType, + false + ); } /** @@ -889,15 +921,33 @@ public class KeyguardIndicationController { * by {@link KeyguardIndicationRotateTextViewController}, see class for rotating message * logic. */ - private void showBiometricMessage(CharSequence biometricMessage, - @Nullable CharSequence biometricMessageFollowUp) { + private void showBiometricMessage( + CharSequence biometricMessage, + @Nullable CharSequence biometricMessageFollowUp, + BiometricSourceType biometricSourceType, + boolean isSuccessMessage + ) { if (TextUtils.equals(biometricMessage, mBiometricMessage) + && biometricSourceType == mBiometricMessageSource && TextUtils.equals(biometricMessageFollowUp, mBiometricMessageFollowUp)) { return; } + if (!isSuccessMessage + && mBiometricMessageSource == FINGERPRINT + && biometricSourceType != FINGERPRINT) { + // drop all non-fingerprint biometric messages if there's a fingerprint message showing + mKeyguardLogger.logDropNonFingerprintMessage( + biometricMessage, + biometricMessageFollowUp, + biometricSourceType + ); + return; + } + mBiometricMessage = biometricMessage; mBiometricMessageFollowUp = biometricMessageFollowUp; + mBiometricMessageSource = biometricSourceType; mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK); hideBiometricMessageDelayed( @@ -914,6 +964,7 @@ public class KeyguardIndicationController { if (mBiometricMessage != null || mBiometricMessageFollowUp != null) { mBiometricMessage = null; mBiometricMessageFollowUp = null; + mBiometricMessageSource = null; mHideBiometricMessageHandler.cancel(); updateBiometricMessage(); } @@ -1085,7 +1136,8 @@ public class KeyguardIndicationController { } else { message = mContext.getString(R.string.keyguard_retry); } - mStatusBarKeyguardViewManager.setKeyguardMessage(message, mInitialTextColorState); + mStatusBarKeyguardViewManager.setKeyguardMessage(message, mInitialTextColorState, + null); } } else { final boolean canSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer( @@ -1097,34 +1149,40 @@ public class KeyguardIndicationController { || mAccessibilityManager.isTouchExplorationEnabled(); if (udfpsSupported && faceAuthenticated) { // co-ex if (a11yEnabled) { - showBiometricMessage( + showSuccessBiometricMessage( mContext.getString(R.string.keyguard_face_successful_unlock), - mContext.getString(R.string.keyguard_unlock) + mContext.getString(R.string.keyguard_unlock), + FACE ); } else { - showBiometricMessage( + showSuccessBiometricMessage( mContext.getString(R.string.keyguard_face_successful_unlock), - mContext.getString(R.string.keyguard_unlock_press) + mContext.getString(R.string.keyguard_unlock_press), + FACE ); } } else if (faceAuthenticated) { // face-only - showBiometricMessage( + showSuccessBiometricMessage( mContext.getString(R.string.keyguard_face_successful_unlock), - mContext.getString(R.string.keyguard_unlock) + mContext.getString(R.string.keyguard_unlock), + FACE ); } else if (udfpsSupported) { // udfps-only if (a11yEnabled) { - showBiometricMessage(mContext.getString(R.string.keyguard_unlock)); + showSuccessBiometricMessage( + mContext.getString(R.string.keyguard_unlock), + null + ); } else { - showBiometricMessage(mContext.getString( - R.string.keyguard_unlock_press)); + showSuccessBiometricMessage(mContext.getString( + R.string.keyguard_unlock_press), null); } } else { // no security or unlocked by a trust agent - showBiometricMessage(mContext.getString(R.string.keyguard_unlock)); + showSuccessBiometricMessage(mContext.getString(R.string.keyguard_unlock), null); } } else { // suggest swiping up for the primary authentication bouncer - showBiometricMessage(mContext.getString(R.string.keyguard_unlock)); + showBiometricMessage(mContext.getString(R.string.keyguard_unlock), null); } } } @@ -1228,6 +1286,13 @@ public class KeyguardIndicationController { && msgId != BIOMETRIC_HELP_FACE_NOT_AVAILABLE; final boolean faceAuthFailed = biometricSourceType == FACE && msgId == BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; // ran through matcher & failed + if (faceAuthFailed && mFaceLockedOutThisAuthSession) { + mKeyguardLogger.logBiometricMessage( + "skipped showing faceAuthFailed message due to lockout", + msgId, + helpString); + return; + } final boolean fpAuthFailed = biometricSourceType == FINGERPRINT && msgId == BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED; // ran matcher & failed final boolean isUnlockWithFingerprintPossible = canUnlockWithFingerprint(); @@ -1245,49 +1310,55 @@ public class KeyguardIndicationController { mBouncerMessageInteractor.setFaceAcquisitionMessage(helpString); } mStatusBarKeyguardViewManager.setKeyguardMessage(helpString, - mInitialTextColorState); + mInitialTextColorState, biometricSourceType); } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) { if (isCoExFaceAcquisitionMessage && msgId == FACE_ACQUIRED_TOO_DARK) { showBiometricMessage( helpString, - mContext.getString(R.string.keyguard_suggest_fingerprint) + mContext.getString(R.string.keyguard_suggest_fingerprint), + biometricSourceType ); } else if (faceAuthFailed && isUnlockWithFingerprintPossible) { showBiometricMessage( mContext.getString(R.string.keyguard_face_failed), - mContext.getString(R.string.keyguard_suggest_fingerprint) + mContext.getString(R.string.keyguard_suggest_fingerprint), + biometricSourceType ); } else if (fpAuthFailed && mKeyguardUpdateMonitor.isCurrentUserUnlockedWithFace()) { // face had already previously unlocked the device, so instead of showing a // fingerprint error, tell them they have already unlocked with face auth // and how to enter their device - showBiometricMessage( + showSuccessBiometricMessage( mContext.getString(R.string.keyguard_face_successful_unlock), - mContext.getString(R.string.keyguard_unlock) + mContext.getString(R.string.keyguard_unlock), + null ); } else if (fpAuthFailed && mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser())) { - showBiometricMessage( + showSuccessBiometricMessage( getTrustGrantedIndication(), - mContext.getString(R.string.keyguard_unlock) + mContext.getString(R.string.keyguard_unlock), + null ); } else if (faceAuthUnavailable) { showBiometricMessage( helpString, isUnlockWithFingerprintPossible ? mContext.getString(R.string.keyguard_suggest_fingerprint) - : mContext.getString(R.string.keyguard_unlock) + : mContext.getString(R.string.keyguard_unlock), + biometricSourceType ); } else { - showBiometricMessage(helpString); + showBiometricMessage(helpString, biometricSourceType); } } else if (faceAuthFailed) { // show action to unlock mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ACTION_TO_UNLOCK), TRANSIENT_BIOMETRIC_ERROR_TIMEOUT); } else { - mBiometricErrorMessageToShowOnScreenOn = helpString; + mBiometricErrorMessageToShowOnScreenOn = + new Pair<>(helpString, biometricSourceType); mHandler.sendMessageDelayed( mHandler.obtainMessage(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON), 1000); @@ -1333,7 +1404,7 @@ public class KeyguardIndicationController { } else if (mIndicationHelper.isFaceLockoutErrorMsg(msgId)) { handleFaceLockoutError(errString); } else { - showErrorMessageNowOrLater(errString, null); + showErrorMessageNowOrLater(errString, null, FACE); } } @@ -1343,7 +1414,7 @@ public class KeyguardIndicationController { msgId, errString); } else { - showErrorMessageNowOrLater(errString, null); + showErrorMessageNowOrLater(errString, null, FINGERPRINT); } } @@ -1371,7 +1442,7 @@ public class KeyguardIndicationController { @Override public void onTrustAgentErrorMessage(CharSequence message) { - showBiometricMessage(message); + showBiometricMessage(message, null); } @Override @@ -1459,12 +1530,13 @@ public class KeyguardIndicationController { // had too many unsuccessful attempts. if (!mFaceLockedOutThisAuthSession) { mFaceLockedOutThisAuthSession = true; - showErrorMessageNowOrLater(errString, followupMessage); + showErrorMessageNowOrLater(errString, followupMessage, FACE); } else if (!mAuthController.isUdfpsFingerDown()) { // On subsequent lockouts, we show a more generic locked out message. showErrorMessageNowOrLater( mContext.getString(R.string.keyguard_face_unlock_unavailable), - followupMessage); + followupMessage, + FACE); } } @@ -1484,7 +1556,8 @@ public class KeyguardIndicationController { && !mStatusBarKeyguardViewManager.isBouncerShowing()) { showBiometricMessage( deferredFaceMessage, - mContext.getString(R.string.keyguard_suggest_fingerprint) + mContext.getString(R.string.keyguard_suggest_fingerprint), + FACE ); } else { // otherwise, don't show any message @@ -1496,7 +1569,8 @@ public class KeyguardIndicationController { // user to manually retry. showBiometricMessage( deferredFaceMessage, - mContext.getString(R.string.keyguard_unlock) + mContext.getString(R.string.keyguard_unlock), + FACE ); } else { // Face-only @@ -1510,13 +1584,15 @@ public class KeyguardIndicationController { getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed(); } - private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) { + private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg, + BiometricSourceType biometricSourceType) { if (mStatusBarKeyguardViewManager.isBouncerShowing()) { - mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState); + mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState, + biometricSourceType); } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) { - showBiometricMessage(errString, followUpMsg); + showBiometricMessage(errString, followUpMsg, biometricSourceType); } else { - mBiometricErrorMessageToShowOnScreenOn = errString; + mBiometricErrorMessageToShowOnScreenOn = new Pair<>(errString, biometricSourceType); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index d10ca3d31de2..6b47ac113928 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -239,10 +239,22 @@ public class PhoneStatusBarView extends FrameLayout { ViewGroup.LayoutParams layoutParams = getLayoutParams(); mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext); layoutParams.height = mStatusBarHeight - waterfallTopInset; + updateSystemIconsContainerHeight(); updatePaddings(); setLayoutParams(layoutParams); } + private void updateSystemIconsContainerHeight() { + View systemIconsContainer = findViewById(R.id.system_icons); + ViewGroup.LayoutParams layoutParams = systemIconsContainer.getLayoutParams(); + int newSystemIconsHeight = + getResources().getDimensionPixelSize(R.dimen.status_bar_system_icons_height); + if (layoutParams.height != newSystemIconsHeight) { + layoutParams.height = newSystemIconsHeight; + systemIconsContainer.setLayoutParams(layoutParams); + } + } + private void updatePaddings() { int statusBarPaddingStart = getResources().getDimensionPixelSize( R.dimen.status_bar_padding_start); 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 29fd2258b40c..d1055c77ab8f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -23,7 +23,6 @@ import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConst import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING; -import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows; import android.content.Context; @@ -98,6 +97,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.unfold.FoldAodAnimationController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.android.systemui.util.kotlin.JavaAdapter; import dagger.Lazy; @@ -348,6 +348,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private Lazy<KeyguardSurfaceBehindInteractor> mSurfaceBehindInteractor; private Lazy<KeyguardDismissActionInteractor> mKeyguardDismissActionInteractor; + private final JavaAdapter mJavaAdapter; + @Inject public StatusBarKeyguardViewManager( Context context, @@ -378,7 +380,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor, Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy, SelectedUserInteractor selectedUserInteractor, - Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor + Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor, + JavaAdapter javaAdapter ) { mContext = context; mViewMediatorCallback = callback; @@ -411,6 +414,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardDismissActionInteractor = keyguardDismissActionInteractorLazy; mSelectedUserInteractor = selectedUserInteractor; mSurfaceBehindInteractor = surfaceBehindInteractor; + mJavaAdapter = javaAdapter; } KeyguardTransitionInteractor mKeyguardTransitionInteractor; @@ -481,8 +485,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (KeyguardWmStateRefactor.isEnabled()) { // Show the keyguard views whenever we've told WM that the lockscreen is visible. - collectFlow( - getViewRootImpl().getView(), + mJavaAdapter.alwaysCollectFlow( combineFlows( mWmLockscreenVisibilityInteractor.get().getLockscreenVisibility(), mSurfaceBehindInteractor.get().isAnimatingSurface(), @@ -781,7 +784,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } updateAlternateBouncerShowing(mAlternateBouncerInteractor.show()); - setKeyguardMessage(message, null); + setKeyguardMessage(message, null, null); return; } @@ -1444,11 +1447,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } /** Display security message to relevant KeyguardMessageArea. */ - public void setKeyguardMessage(String message, ColorStateList colorState) { + public void setKeyguardMessage(String message, ColorStateList colorState, + BiometricSourceType biometricSourceType) { if (mAlternateBouncerInteractor.isVisibleState()) { if (mKeyguardMessageAreaController != null) { DeviceEntryUdfpsRefactor.assertInLegacyMode(); - mKeyguardMessageAreaController.setMessage(message); + mKeyguardMessageAreaController.setMessage(message, biometricSourceType); } } else { mPrimaryBouncerInteractor.showMessage(message, colorState); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index 65dede83f3d6..cb61534b2255 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -28,6 +28,7 @@ import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_N import static com.android.server.notification.Flags.screenshareNotificationHiding; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.app.INotificationManager; import android.app.Notification; @@ -50,6 +51,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.flags.FeatureFlags; @@ -97,6 +99,7 @@ public class BubblesManager { private final Context mContext; private final Bubbles mBubbles; private final NotificationShadeWindowController mNotificationShadeWindowController; + private final KeyguardStateController mKeyguardStateController; private final ShadeController mShadeController; private final IStatusBarService mBarService; private final INotificationManager mNotificationManager; @@ -109,6 +112,7 @@ public class BubblesManager { private final NotifPipeline mNotifPipeline; private final NotifPipelineFlags mNotifPipelineFlags; private final Executor mSysuiMainExecutor; + private final Executor mSysuiUiBgExecutor; private final Bubbles.SysuiProxy mSysuiProxy; // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline @@ -116,6 +120,8 @@ public class BubblesManager { private final StatusBarWindowCallback mStatusBarWindowCallback; private final Runnable mSensitiveStateChangedListener; private boolean mPanelExpanded; + private boolean mKeyguardShowing; + private boolean mDreamingOrInPreview; /** * Creates {@link BubblesManager}, returns {@code null} if Optional {@link Bubbles} not present @@ -140,7 +146,8 @@ public class BubblesManager { SysUiState sysUiState, FeatureFlags featureFlags, NotifPipelineFlags notifPipelineFlags, - Executor sysuiMainExecutor) { + Executor sysuiMainExecutor, + Executor sysuiUiBgExecutor) { if (bubblesOptional.isPresent()) { return new BubblesManager(context, bubblesOptional.get(), @@ -160,7 +167,8 @@ public class BubblesManager { sysUiState, featureFlags, notifPipelineFlags, - sysuiMainExecutor); + sysuiMainExecutor, + sysuiUiBgExecutor); } else { return null; } @@ -185,10 +193,12 @@ public class BubblesManager { SysUiState sysUiState, FeatureFlags featureFlags, NotifPipelineFlags notifPipelineFlags, - Executor sysuiMainExecutor) { + Executor sysuiMainExecutor, + Executor sysuiUiBgExecutor) { mContext = context; mBubbles = bubbles; mNotificationShadeWindowController = notificationShadeWindowController; + mKeyguardStateController = keyguardStateController; mShadeController = shadeController; mNotificationManager = notificationManager; mDreamManager = dreamManager; @@ -200,6 +210,7 @@ public class BubblesManager { mNotifPipeline = notifPipeline; mNotifPipelineFlags = notifPipelineFlags; mSysuiMainExecutor = sysuiMainExecutor; + mSysuiUiBgExecutor = sysuiUiBgExecutor; mBarService = statusBarService == null ? IStatusBarService.Stub.asInterface( @@ -208,12 +219,11 @@ public class BubblesManager { setupNotifPipeline(); - keyguardStateController.addCallback(new KeyguardStateController.Callback() { + // TODO(b/327410864): use KeyguardTransitionInteractor to listen for keyguard changes + mKeyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override public void onKeyguardShowingChanged() { - boolean isUnlockedShade = !keyguardStateController.isShowing() - && !isDreamingOrInPreview(); - bubbles.onStatusBarStateChanged(isUnlockedShade); + updateKeyguardAndDreamingState(); } }); @@ -256,6 +266,14 @@ public class BubblesManager { mPanelExpanded = panelExpanded; mBubbles.onNotificationPanelExpandedChanged(panelExpanded); } + if (!mKeyguardShowing && mDreamingOrInPreview && !isDreaming) { + // We check for dreaming state changes when keyguard status changes. + // This causes us to miss events if dreaming state changes after keyguard. + // Add a check here for the case where keyguard is dismissed before + // dreaming state changes. Otherwise bubbles remain invisible. + // TODO(b/327410864): use KeyguardTransitionInteractor for dreaming changes + updateKeyguardAndDreamingState(); + } }; notificationShadeWindowController.registerCallback(mStatusBarWindowCallback); @@ -395,6 +413,19 @@ public class BubblesManager { mBubbles.setSysuiProxy(mSysuiProxy); } + private void updateKeyguardAndDreamingState() { + mSysuiUiBgExecutor.execute(() -> { + mKeyguardShowing = mKeyguardStateController.isShowing(); + mDreamingOrInPreview = isDreamingOrInPreview(); + boolean isUnlockedShade = !mKeyguardShowing && !mDreamingOrInPreview; + ProtoLog.d(WM_SHELL_BUBBLES, + "handleKeyguardOrDreamChange isUnlockedShade=%b keyguardShowing=%b " + + "dreamingOrInPreview=%b", + isUnlockedShade, mKeyguardShowing, mDreamingOrInPreview); + mBubbles.onStatusBarStateChanged(isUnlockedShade); + }); + } + private boolean isDreamingOrInPreview() { try { return mDreamManager.isDreamingOrInPreview(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java index d4522d003d52..93e7602715b1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java @@ -19,11 +19,14 @@ package com.android.keyguard; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.hardware.biometrics.BiometricSourceType; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -119,4 +122,51 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase { when(mKeyguardMessageArea.getText()).thenReturn(msg); assertThat(mMessageAreaController.getMessage()).isEqualTo(msg); } + + @Test + public void testFingerprintMessageUpdate() { + String msg = "fpMessage"; + mMessageAreaController.setMessage( + msg, BiometricSourceType.FINGERPRINT + ); + verify(mKeyguardMessageArea).setMessage(msg, /* animate= */ true); + + String msg2 = "fpMessage2"; + mMessageAreaController.setMessage( + msg2, BiometricSourceType.FINGERPRINT + ); + verify(mKeyguardMessageArea).setMessage(msg2, /* animate= */ true); + } + + @Test + public void testFaceMessageDroppedWhileFingerprintMessageShowing() { + String fpMsg = "fpMessage"; + mMessageAreaController.setMessage( + fpMsg, BiometricSourceType.FINGERPRINT + ); + verify(mKeyguardMessageArea).setMessage(eq(fpMsg), /* animate= */ anyBoolean()); + + String faceMessage = "faceMessage"; + mMessageAreaController.setMessage( + faceMessage, BiometricSourceType.FACE + ); + verify(mKeyguardMessageArea, never()) + .setMessage(eq(faceMessage), /* animate= */ anyBoolean()); + } + + @Test + public void testGenericMessageShowsAfterFingerprintMessageShowing() { + String fpMsg = "fpMessage"; + mMessageAreaController.setMessage( + fpMsg, BiometricSourceType.FINGERPRINT + ); + verify(mKeyguardMessageArea).setMessage(eq(fpMsg), /* animate= */ anyBoolean()); + + String genericMessage = "genericMessage"; + mMessageAreaController.setMessage( + genericMessage, null + ); + verify(mKeyguardMessageArea) + .setMessage(eq(genericMessage), /* animate= */ anyBoolean()); + } } 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 2c1a87d86be9..10b86ea9fd31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -21,8 +21,8 @@ import android.hardware.biometrics.BiometricAuthenticator import android.hardware.biometrics.BiometricConstants import android.hardware.biometrics.BiometricManager import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT -import android.hardware.biometrics.Flags.customBiometricPrompt import android.hardware.biometrics.PromptInfo +import android.hardware.biometrics.PromptVerticalListContentView import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.os.Handler @@ -40,7 +40,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.widget.LockPatternUtils -import com.android.systemui.Flags.FLAG_CONSTRAINT_BP import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository @@ -148,8 +147,6 @@ open class AuthContainerViewTest : SysuiTestCase() { @Before fun setup() { - mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP) displayRepository = FakeDisplayRepository() displayStateInteractor = @@ -388,9 +385,10 @@ open class AuthContainerViewTest : SysuiTestCase() { } @Test - fun testShowCredentialUI() { + fun testShowCredentialUI_withDescription() { + mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) val container = initializeFingerprintContainer( - authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL + authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL ) waitForIdleSync() @@ -399,16 +397,12 @@ open class AuthContainerViewTest : SysuiTestCase() { } @Test - fun testShowBiometricUIWhenCustomBpEnabledAndNoSensors() { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + fun testShowCredentialUI_withCustomBp() { val container = initializeFingerprintContainer( - authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL + authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL, + isUsingContentView = true ) - waitForIdleSync() - - assertThat(customBiometricPrompt()).isTrue() - assertThat(container.hasBiometricPrompt()).isTrue() - assertThat(container.hasCredentialView()).isFalse() + checkBpShowsForCredentialAndGoToCredential(container) } @Test @@ -512,11 +506,13 @@ open class AuthContainerViewTest : SysuiTestCase() { private fun initializeFingerprintContainer( authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK, - addToView: Boolean = true + addToView: Boolean = true, + isUsingContentView: Boolean = false, ) = initializeContainer( TestAuthContainerView( authenticators = authenticators, - fingerprintProps = fingerprintSensorPropertiesInternal() + fingerprintProps = fingerprintSensorPropertiesInternal(), + isUsingContentView = isUsingContentView, ), addToView ) @@ -549,7 +545,8 @@ open class AuthContainerViewTest : SysuiTestCase() { private inner class TestAuthContainerView( authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK, fingerprintProps: List<FingerprintSensorPropertiesInternal> = listOf(), - faceProps: List<FaceSensorPropertiesInternal> = listOf() + faceProps: List<FaceSensorPropertiesInternal> = listOf(), + isUsingContentView: Boolean = false, ) : AuthContainerView( Config().apply { mContext = this@AuthContainerViewTest.context @@ -559,6 +556,9 @@ open class AuthContainerViewTest : SysuiTestCase() { mSkipAnimation = true mPromptInfo = PromptInfo().apply { this.authenticators = authenticators + if (isUsingContentView) { + this.contentView = PromptVerticalListContentView.Builder().build() + } } mOpPackageName = OP_PACKAGE_NAME }, @@ -614,6 +614,17 @@ open class AuthContainerViewTest : SysuiTestCase() { val layoutParams = AuthContainerView.getLayoutParams(windowToken, "") assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.systemBars()) == 0).isTrue() } + + private fun checkBpShowsForCredentialAndGoToCredential(container: TestAuthContainerView) { + waitForIdleSync() + assertThat(container.hasBiometricPrompt()).isTrue() + assertThat(container.hasCredentialView()).isFalse() + + container.animateToCredentialUI(false) + waitForIdleSync() + assertThat(container.hasBiometricPrompt()).isFalse() + assertThat(container.hasCredentialView()).isTrue() + } } private fun AuthContainerView.hasBiometricPrompt() = diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt index b39e09df9d2e..7b972d3707af 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt @@ -16,7 +16,9 @@ package com.android.systemui.biometrics.data.repository +import android.hardware.biometrics.BiometricManager import android.hardware.biometrics.PromptInfo +import android.hardware.biometrics.PromptVerticalListContentView import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController @@ -131,6 +133,52 @@ class PromptRepositoryImplTest : SysuiTestCase() { } @Test + fun showBpWithoutIconForCredential_withCustomBp() = + testScope.runTest { + for (case in + listOf( + PromptKind.Biometric(), + PromptKind.Pin, + PromptKind.Password, + PromptKind.Pattern + )) { + val hasCredentialViewShown = case !is PromptKind.Biometric + val promptInfo = + PromptInfo().apply { + authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL + contentView = PromptVerticalListContentView.Builder().build() + } + repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME) + repository.setShouldShowBpWithoutIconForCredential(promptInfo) + + assertThat(repository.showBpWithoutIconForCredential.value) + .isEqualTo(!hasCredentialViewShown) + } + } + + @Test + fun showBpWithoutIconForCredential_withDescription() = + testScope.runTest { + for (case in + listOf( + PromptKind.Biometric(), + PromptKind.Pin, + PromptKind.Password, + PromptKind.Pattern + )) { + val promptInfo = + PromptInfo().apply { + authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL + description = "description" + } + repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME) + repository.setShouldShowBpWithoutIconForCredential(promptInfo) + + assertThat(repository.showBpWithoutIconForCredential.value).isFalse() + } + } + + @Test fun setsAndUnsetsPrompt() = testScope.runTest { val kind = PromptKind.Pin diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt index 52b42750847a..2817780cbf03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt @@ -147,7 +147,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PIN) } @Test - fun usePattermCredentialAndReset() = + fun usePatternCredentialAndReset() = testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PATTERN) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt index 6d8e7aa0703c..6aebe365dc8c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt @@ -126,7 +126,7 @@ class FromLockscreenTransitionInteractorTest : SysuiTestCase() { keyguardRepository.setKeyguardDismissible(true) runCurrent() shadeRepository.setCurrentFling( - FlingInfo(expand = true) // Not a dismiss fling (expand = true). + FlingInfo(expand = false) // Is a dismiss fling upward (expand = false). ) runCurrent() @@ -153,4 +153,22 @@ class FromLockscreenTransitionInteractorTest : SysuiTestCase() { assertThatRepository(transitionRepository).noTransitionsStarted() } + + @Test + @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testDoesNotTransitionToGone_whenDismissFling_emitsNull() = + testScope.runTest { + underTest.start() + verify(transitionRepository, never()).startTransition(any()) + + keyguardRepository.setKeyguardDismissible(true) + runCurrent() + + // The fling is null when it a) initializes b) ends and in either case we should not + // swipe to unlock. + shadeRepository.setCurrentFling(null) + runCurrent() + + assertThatRepository(transitionRepository).noTransitionsStarted() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt index a4483bdac467..6d605a564022 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt @@ -375,4 +375,323 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { values ) } + + @Test + fun testLockscreenVisibility_usesFromState_ifCanceled() = + testScope.runTest { + val values by collectValues(underTest.lockscreenVisibility) + + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope + ) + + runCurrent() + + assertEquals( + listOf( + // Initially should be true, as we start in LOCKSCREEN. + true, + // Then, false, since we finish in GONE. + false, + ), + values + ) + + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + from = KeyguardState.GONE, + to = KeyguardState.AOD, + ) + ) + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.RUNNING, + from = KeyguardState.GONE, + to = KeyguardState.AOD, + ) + ) + runCurrent() + + assertEquals( + listOf( + true, + // Should remain false as we transition from GONE. + false, + ), + values + ) + + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.CANCELED, + from = KeyguardState.GONE, + to = KeyguardState.AOD, + ) + ) + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + ) + ) + + runCurrent() + + assertEquals( + listOf( + true, + false, + // If we cancel and then go from LS -> GONE, we should immediately flip to the + // visibility of the from state (LS). + true, + ), + values + ) + + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.FINISHED, + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + ) + ) + + runCurrent() + + assertEquals( + listOf( + true, + false, + true, + ), + values + ) + } + + /** + * Tests the special case for insecure camera launch. CANCELING a transition from GONE and then + * STARTING a transition back to GONE should never show the lockscreen, even though the current + * state during the AOD/isAsleep -> GONE transition is AOD (where lockscreen visibility = true). + */ + @Test + fun testLockscreenVisibility_falseDuringTransitionToGone_fromCanceledGone() = + testScope.runTest { + val values by collectValues(underTest.lockscreenVisibility) + + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope + ) + + runCurrent() + assertEquals( + listOf( + true, + // Not visible since we're GONE. + false, + ), + values + ) + + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + from = KeyguardState.GONE, + to = KeyguardState.AOD, + ) + ) + runCurrent() + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.RUNNING, + from = KeyguardState.GONE, + to = KeyguardState.AOD, + ) + ) + runCurrent() + + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.CANCELED, + from = KeyguardState.GONE, + to = KeyguardState.AOD, + ) + ) + runCurrent() + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + from = KeyguardState.AOD, + to = KeyguardState.GONE, + ) + ) + runCurrent() + + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.RUNNING, + from = KeyguardState.AOD, + to = KeyguardState.GONE, + ) + ) + runCurrent() + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.FINISHED, + from = KeyguardState.AOD, + to = KeyguardState.GONE, + ) + ) + + runCurrent() + assertEquals( + listOf( + true, + // Remains not visible from GONE -> AOD (canceled) -> AOD since we never + // FINISHED in AOD, and special-case handling for the insecure camera launch + // ensures that we use the lockscreen visibility for GONE (false) if we're + // STARTED to GONE after a CANCELED from GONE. + false, + ), + values + ) + + transitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + + assertEquals( + listOf( + true, + false, + // Make sure there's no stuck overrides or something - we should make lockscreen + // visible again once we're finished in LOCKSCREEN. + true, + ), + values + ) + } + + /** */ + @Test + fun testLockscreenVisibility_trueDuringTransitionToGone_fromNotCanceledGone() = + testScope.runTest { + val values by collectValues(underTest.lockscreenVisibility) + + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope + ) + + runCurrent() + assertEquals( + listOf( + true, + // Not visible when finished in GONE. + false, + ), + values + ) + + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + from = KeyguardState.GONE, + to = KeyguardState.AOD, + ) + ) + runCurrent() + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.RUNNING, + from = KeyguardState.GONE, + to = KeyguardState.AOD, + ) + ) + runCurrent() + + assertEquals( + listOf( + true, + // Still not visible during GONE -> AOD. + false, + ), + values + ) + + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.FINISHED, + from = KeyguardState.GONE, + to = KeyguardState.AOD, + ) + ) + runCurrent() + + assertEquals( + listOf( + true, + false, + // Visible now that we're FINISHED in AOD. + true + ), + values + ) + + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + from = KeyguardState.AOD, + to = KeyguardState.GONE, + ) + ) + runCurrent() + + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.RUNNING, + from = KeyguardState.AOD, + to = KeyguardState.GONE, + ) + ) + runCurrent() + + assertEquals( + listOf( + true, + false, + // Remains visible from AOD during transition. + true + ), + values + ) + + transitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.FINISHED, + from = KeyguardState.AOD, + to = KeyguardState.GONE, + ) + ) + + runCurrent() + assertEquals( + listOf( + true, + false, + true, + // Until we're finished in GONE again. + false + ), + values + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt index 45f49f01a43e..fa28036f274b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt @@ -24,11 +24,8 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase -import com.android.systemui.communal.domain.interactor.communalInteractor -import com.android.systemui.communal.domain.interactor.setCommunalAvailable -import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.ui.viewmodel.fakeCommunalTransitionViewModel import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle @@ -115,10 +112,10 @@ class MediaHierarchyManagerTest : SysuiTestCase() { private lateinit var isQsBypassingShade: MutableStateFlow<Boolean> private lateinit var mediaFrame: ViewGroup private val configurationController = FakeConfigurationController() - private val communalInteractor = kosmos.communalInteractor private val settings = FakeSettings() private lateinit var testableLooper: TestableLooper private lateinit var fakeHandler: FakeHandler + private var communalTransitionViewModel = kosmos.fakeCommunalTransitionViewModel @Before fun setup() { @@ -142,7 +139,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { mediaDataManager, keyguardViewController, dreamOverlayStateController, - communalInteractor, + communalTransitionViewModel, configurationController, wakefulnessLifecycle, shadeInteractor, @@ -510,11 +507,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @Test fun testCommunalLocation() = testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_COMMUNAL_HUB) - kosmos.setCommunalAvailable(true) - runCurrent() - - communalInteractor.onSceneChanged(CommunalSceneKey.Communal) + communalTransitionViewModel.setIsUmoOnCommunal(true) runCurrent() verify(mediaCarouselController) .onDesiredLocationChanged( @@ -526,7 +519,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { ) clearInvocations(mediaCarouselController) - communalInteractor.onSceneChanged(CommunalSceneKey.Blank) + communalTransitionViewModel.setIsUmoOnCommunal(false) runCurrent() verify(mediaCarouselController) .onDesiredLocationChanged( @@ -541,15 +534,11 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @Test fun testCommunalLocation_showsOverLockscreen() = testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_COMMUNAL_HUB) - kosmos.setCommunalAvailable(true) - runCurrent() - // Device is on lock screen. whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) // UMO goes to communal even over the lock screen. - communalInteractor.onSceneChanged(CommunalSceneKey.Communal) + communalTransitionViewModel.setIsUmoOnCommunal(true) runCurrent() verify(mediaCarouselController) .onDesiredLocationChanged( @@ -564,14 +553,10 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @Test fun testCommunalLocation_showsUntilQsExpands() = testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_COMMUNAL_HUB) - kosmos.setCommunalAvailable(true) - runCurrent() - // Device is on lock screen. whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - communalInteractor.onSceneChanged(CommunalSceneKey.Communal) + communalTransitionViewModel.setIsUmoOnCommunal(true) runCurrent() verify(mediaCarouselController) .onDesiredLocationChanged( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index aa53558e858d..1504d4c1f033 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -92,6 +92,21 @@ import java.util.Set; @TestableLooper.RunWithLooper public class KeyguardIndicationControllerTest extends KeyguardIndicationControllerBaseTest { @Test + public void afterFaceLockout_skipShowingFaceNotRecognized() { + createController(); + onFaceLockoutError("lockout"); + verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "lockout"); + clearInvocations(mRotateTextViewController); + + // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED (face fail) + mKeyguardUpdateMonitorCallback.onBiometricHelp( + BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, + "Face not recognized", + BiometricSourceType.FACE); + verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); // no updated message + } + + @Test public void createController_setIndicationAreaAgain_destroysPreviousRotateTextViewController() { // GIVEN a controller with a mocked rotate text view controlller final KeyguardIndicationRotateTextViewController mockedRotateTextViewController = @@ -593,7 +608,7 @@ public class KeyguardIndicationControllerTest extends KeyguardIndicationControll mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT, "A message", BiometricSourceType.FACE); - verify(mStatusBarKeyguardViewManager).setKeyguardMessage(eq(message), any()); + verify(mStatusBarKeyguardViewManager).setKeyguardMessage(eq(message), any(), any()); } @Test @@ -608,7 +623,8 @@ public class KeyguardIndicationControllerTest extends KeyguardIndicationControll mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT, "A message", BiometricSourceType.FACE); - verify(mStatusBarKeyguardViewManager, never()).setKeyguardMessage(eq(message), any()); + verify(mStatusBarKeyguardViewManager, never()).setKeyguardMessage( + eq(message), any(), any()); } @Test @@ -1242,7 +1258,7 @@ public class KeyguardIndicationControllerTest extends KeyguardIndicationControll public void onBiometricFailed_resetFaceHelpMessageDeferral() { createController(); - // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED + // WHEN face sends an onBiometricAuthFailed mKeyguardUpdateMonitorCallback.onBiometricAuthFailed(BiometricSourceType.FACE); // THEN face help message deferral is reset @@ -1331,7 +1347,9 @@ public class KeyguardIndicationControllerTest extends KeyguardIndicationControll verify(mStatusBarKeyguardViewManager) .setKeyguardMessage( eq(mContext.getString(R.string.keyguard_face_unlock_unavailable)), - any()); + any(), + any() + ); } @Test @@ -1471,6 +1489,71 @@ public class KeyguardIndicationControllerTest extends KeyguardIndicationControll mContext.getString(R.string.keyguard_suggest_fingerprint)); } + @Test + public void faceErrorMessageDroppedBecauseFingerprintMessageShowing() { + createController(); + mController.setVisible(true); + mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED, + "fp not recognized", BiometricSourceType.FINGERPRINT); + clearInvocations(mRotateTextViewController); + + onFaceLockoutError("lockout"); + verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); + } + + @Test + public void faceUnlockedMessageShowsEvenWhenFingerprintMessageShowing() { + createController(); + mController.setVisible(true); + mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED, + "fp not recognized", BiometricSourceType.FINGERPRINT); + clearInvocations(mRotateTextViewController); + + when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true); + when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser())) + .thenReturn(true); + mController.getKeyguardCallback().onBiometricAuthenticated(0, + BiometricSourceType.FACE, false); + verifyIndicationMessage( + INDICATION_TYPE_BIOMETRIC_MESSAGE, + mContext.getString(R.string.keyguard_face_successful_unlock)); + } + + @Test + public void onTrustAgentErrorMessageDroppedBecauseFingerprintMessageShowing() { + createController(); + mController.setVisible(true); + mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED, + "fp not recognized", BiometricSourceType.FINGERPRINT); + clearInvocations(mRotateTextViewController); + + mKeyguardUpdateMonitorCallback.onTrustAgentErrorMessage("testMessage"); + verifyNoMessage(INDICATION_TYPE_TRUST); + verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); + } + + @Test + public void trustGrantedMessageShowsEvenWhenFingerprintMessageShowing() { + createController(); + mController.setVisible(true); + mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED, + "fp not recognized", BiometricSourceType.FINGERPRINT); + clearInvocations(mRotateTextViewController); + + // GIVEN trust is granted + when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true); + + // WHEN the showTrustGranted method is called + final String trustGrantedMsg = "testing trust granted message after fp message"; + mController.getKeyguardCallback().onTrustGrantedForCurrentUser( + false, false, new TrustGrantFlags(0), trustGrantedMsg); + + // THEN verify the trust granted message shows + verifyIndicationMessage( + INDICATION_TYPE_TRUST, + trustGrantedMsg); + } + private void screenIsTurningOn() { when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_TURNING_ON); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt index 5e8b62e799c1..fd2dead02c6c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt @@ -26,6 +26,7 @@ import android.view.LayoutInflater import android.view.MotionEvent import android.view.PrivacyIndicatorBounds import android.view.RoundedCorners +import android.view.View import android.view.WindowInsets import android.widget.FrameLayout import androidx.test.filters.SmallTest @@ -50,6 +51,8 @@ import org.mockito.Mockito.verify class PhoneStatusBarViewTest : SysuiTestCase() { private lateinit var view: PhoneStatusBarView + private val systemIconsContainer: View + get() = view.requireViewById(R.id.system_icons) private val contentInsetsProvider = mock<StatusBarContentInsetsProvider>() private val windowController = mock<StatusBarWindowController>() @@ -62,6 +65,7 @@ class PhoneStatusBarViewTest : SysuiTestCase() { ) mDependency.injectTestDependency(DarkIconDispatcher::class.java, mock<DarkIconDispatcher>()) mDependency.injectTestDependency(StatusBarWindowController::class.java, windowController) + context.ensureTestableResources() view = spy(createStatusBarView()) whenever(view.rootWindowInsets).thenReturn(emptyWindowInsets()) whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) @@ -217,7 +221,7 @@ class PhoneStatusBarViewTest : SysuiTestCase() { val newInsets = Insets.NONE whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(newInsets) + .thenReturn(newInsets) view.onConfigurationChanged(Configuration()) assertThat(view.paddingLeft).isEqualTo(previousInsets.left) @@ -239,7 +243,7 @@ class PhoneStatusBarViewTest : SysuiTestCase() { val newInsets = Insets.NONE whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(newInsets) + .thenReturn(newInsets) configuration.densityDpi = 456 view.onConfigurationChanged(configuration) @@ -262,7 +266,7 @@ class PhoneStatusBarViewTest : SysuiTestCase() { val newInsets = Insets.NONE whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) - .thenReturn(newInsets) + .thenReturn(newInsets) configuration.fontScale = 2f view.onConfigurationChanged(configuration) @@ -273,6 +277,19 @@ class PhoneStatusBarViewTest : SysuiTestCase() { } @Test + fun onConfigurationChanged_systemIconsHeightChanged_containerHeightIsUpdated() { + val newHeight = 123456 + context.orCreateTestableResources.addOverride( + R.dimen.status_bar_system_icons_height, + newHeight + ) + + view.onConfigurationChanged(Configuration()) + + assertThat(systemIconsContainer.layoutParams.height).isEqualTo(newHeight) + } + + @Test fun onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsets() { val insets = Insets.of(/* left = */ 90, /* top = */ 10, /* right = */ 45, /* bottom = */ 50) whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) 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 bd7406ad004b..3666248d1783 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 @@ -97,6 +97,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.android.systemui.util.kotlin.JavaAdapter; import com.google.common.truth.Truth; @@ -222,7 +223,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { () -> mock(WindowManagerLockscreenVisibilityInteractor.class), () -> mock(KeyguardDismissActionInteractor.class), mSelectedUserInteractor, - () -> mock(KeyguardSurfaceBehindInteractor.class)) { + () -> mock(KeyguardSurfaceBehindInteractor.class), + mock(JavaAdapter.class)) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -730,7 +732,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { () -> mock(WindowManagerLockscreenVisibilityInteractor.class), () -> mock(KeyguardDismissActionInteractor.class), mSelectedUserInteractor, - () -> mock(KeyguardSurfaceBehindInteractor.class)) { + () -> mock(KeyguardSurfaceBehindInteractor.class), + mock(JavaAdapter.class)) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index a9308601a314..fbefb0eedfa8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -609,6 +609,7 @@ public class BubblesTest extends SysuiTestCase { mSysUiState, mFeatureFlags, mNotifPipelineFlags, + syncExecutor, syncExecutor); mBubblesManager.addNotifCallback(mNotifCallback); @@ -651,7 +652,7 @@ public class BubblesTest extends SysuiTestCase { } @Test - public void dreamingHidesBubbles() throws RemoteException { + public void bubblesHiddenWhileDreaming() throws RemoteException { mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.VISIBLE); @@ -662,7 +663,17 @@ public class BubblesTest extends SysuiTestCase { mKeyguardStateControllerCallbackCaptor.getValue(); callback.onKeyguardShowingChanged(); + // Dreaming should hide bubbles assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.INVISIBLE); + + // Finish dreaming should show bubbles + mNotificationShadeWindowController.setDreaming(false); + when(mIDreamManager.isDreamingOrInPreview()).thenReturn(false); // dreaming finished + + // Dreaming updates come through mNotificationShadeWindowController + mNotificationShadeWindowController.notifyStateChangedCallbacks(); + + assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.VISIBLE); } @Test diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt index f192de23fecc..c3af437dafdf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt @@ -1,6 +1,8 @@ package com.android.systemui.biometrics.data.repository +import android.hardware.biometrics.Flags import android.hardware.biometrics.PromptInfo +import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.shared.model.PromptKind import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -26,6 +28,9 @@ class FakePromptRepository : PromptRepository { private val _isConfirmationRequired = MutableStateFlow(false) override val isConfirmationRequired = _isConfirmationRequired.asStateFlow() + private val _showBpWithoutIconForCredential = MutableStateFlow(false) + override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow() + private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null) override val opPackageName = _opPackageName.asStateFlow() @@ -69,6 +74,16 @@ class FakePromptRepository : PromptRepository { _isConfirmationRequired.value = false } + override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) { + val hasCredentialViewShown = kind.value !is PromptKind.Biometric + val showBpForCredential = + Flags.customBiometricPrompt() && + !Utils.isBiometricAllowed(promptInfo) && + Utils.isDeviceCredentialAllowed(promptInfo) && + promptInfo.contentView != null + _showBpWithoutIconForCredential.value = showBpForCredential && !hasCredentialViewShown + } + fun setIsShowing(showing: Boolean) { _isShowing.value = showing } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt new file mode 100644 index 000000000000..5c3e1f410e63 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 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.bouncer.shared.flag + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags + +var Kosmos.fakeComposeBouncerFlags by + Kosmos.Fixture { FakeComposeBouncerFlags(fakeSceneContainerFlags) } +val Kosmos.composeBouncerFlags by Kosmos.Fixture<ComposeBouncerFlags> { fakeComposeBouncerFlags } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt new file mode 100644 index 000000000000..c116bbd32f9e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 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.bouncer.shared.flag + +import com.android.systemui.scene.shared.flag.SceneContainerFlags + +class FakeComposeBouncerFlags( + private val sceneContainerFlags: SceneContainerFlags, + var composeBouncerEnabled: Boolean = false +) : ComposeBouncerFlags { + override fun isComposeBouncerOrSceneContainerEnabled(): Boolean { + return sceneContainerFlags.isEnabled() || composeBouncerEnabled + } + + @Deprecated( + "Avoid using this, this is meant to be used only by the glue code " + + "that includes compose bouncer in legacy keyguard.", + replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()") + ) + override fun isOnlyComposeBouncerEnabled(): Boolean = composeBouncerEnabled +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt index 99dfe94af3df..6d97238ba48b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt @@ -21,12 +21,12 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor +import com.android.systemui.bouncer.shared.flag.composeBouncerFlags import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope -import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.user.domain.interactor.selectedUserInteractor import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel import com.android.systemui.util.mockito.mock @@ -42,7 +42,7 @@ val Kosmos.bouncerViewModel by Fixture { simBouncerInteractor = simBouncerInteractor, authenticationInteractor = authenticationInteractor, selectedUserInteractor = selectedUserInteractor, - flags = sceneContainerFlags, + flags = composeBouncerFlags, selectedUser = userSwitcherViewModel.selectedUser, users = userSwitcherViewModel.users, userSwitcherMenu = userSwitcherViewModel.menu, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt new file mode 100644 index 000000000000..eaa657b76c3d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 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.communal.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.communalTransitionViewModel by + Kosmos.Fixture<CommunalTransitionViewModel> { fakeCommunalTransitionViewModel } + +val Kosmos.fakeCommunalTransitionViewModel by Kosmos.Fixture { FakeCommunalTransitionViewModel() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/FakeCommunalTransitionViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/FakeCommunalTransitionViewModel.kt new file mode 100644 index 000000000000..409cc1d03fda --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/FakeCommunalTransitionViewModel.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 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.communal.ui.viewmodel + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeCommunalTransitionViewModel : CommunalTransitionViewModel { + private val _isUmoOnCommunal = MutableStateFlow(false) + override val isUmoOnCommunal: Flow<Boolean> = _isUmoOnCommunal + + fun setIsUmoOnCommunal(value: Boolean) { + _isUmoOnCommunal.value = value + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt index 96de4bae63d4..8da5dd46b62a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.biometrics.authController import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.policy.splitShadeStateController val Kosmos.lockscreenContentViewModel by Kosmos.Fixture { @@ -28,5 +29,6 @@ val Kosmos.lockscreenContentViewModel by interactor = keyguardBlueprintInteractor, authController = authController, longPress = keyguardLongPressViewModel, + splitShadeStateController = splitShadeStateController, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt new file mode 100644 index 000000000000..df6fc41e5f3e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.settings + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.fakeGlobalSettings: FakeGlobalSettings by Fixture { FakeGlobalSettings() } diff --git a/proto/src/criticalevents/critical_event_log.proto b/proto/src/criticalevents/critical_event_log.proto index cffcd0941df8..71d291a6f877 100644 --- a/proto/src/criticalevents/critical_event_log.proto +++ b/proto/src/criticalevents/critical_event_log.proto @@ -61,6 +61,12 @@ message CriticalEventProto { NativeCrash native_crash = 6; SystemServerStarted system_server_started = 7; InstallPackages install_packages = 8; + ExcessiveBinderCalls excessive_binder_calls = 9; + } + + message ExcessiveBinderCalls { + // The uid sending many calls. + optional int32 uid = 1; } message InstallPackages {} diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index d4ff699c88a7..6b5ba96f6b1b 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -50,7 +50,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; import android.app.ActivityThread; -import android.app.admin.DevicePolicyManagerInternal; +import android.app.admin.DevicePolicyCache; import android.app.assist.ActivityId; import android.content.ComponentName; import android.content.ContentCaptureOptions; @@ -940,7 +940,7 @@ public class ContentCaptureManagerService extends return new ContentProtectionConsentManager( BackgroundThread.getHandler(), getContext().getContentResolver(), - LocalServices.getService(DevicePolicyManagerInternal.class)); + DevicePolicyCache.getInstance()); } @Nullable diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java index 488a51a56fee..9aa5d2fce6b8 100644 --- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java +++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java @@ -16,8 +16,13 @@ package com.android.server.contentprotection; +import static android.view.contentprotection.flags.Flags.manageDevicePolicyEnabled; + import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.admin.DevicePolicyCache; +import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.ContentResolver; import android.database.ContentObserver; @@ -28,6 +33,7 @@ import android.provider.Settings; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; /** * Manages consent for content protection. @@ -45,6 +51,8 @@ public class ContentProtectionConsentManager { @NonNull private final ContentResolver mContentResolver; + @NonNull private final DevicePolicyCache mDevicePolicyCache; + @NonNull private final DevicePolicyManagerInternal mDevicePolicyManagerInternal; @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @@ -53,54 +61,98 @@ public class ContentProtectionConsentManager { private volatile boolean mCachedPackageVerifierConsent; - private volatile boolean mCachedContentProtectionConsent; + private volatile boolean mCachedContentProtectionUserConsent; public ContentProtectionConsentManager( @NonNull Handler handler, @NonNull ContentResolver contentResolver, - @NonNull DevicePolicyManagerInternal devicePolicyManagerInternal) { + @NonNull DevicePolicyCache devicePolicyCache) { mContentResolver = contentResolver; - mDevicePolicyManagerInternal = devicePolicyManagerInternal; + mDevicePolicyCache = devicePolicyCache; + mDevicePolicyManagerInternal = LocalServices.getService(DevicePolicyManagerInternal.class); mContentObserver = new SettingsObserver(handler); - contentResolver.registerContentObserver( - Settings.Global.getUriFor(KEY_PACKAGE_VERIFIER_USER_CONSENT), - /* notifyForDescendants= */ false, - mContentObserver, - UserHandle.USER_ALL); - - mCachedPackageVerifierConsent = isPackageVerifierConsentGranted(); - mCachedContentProtectionConsent = isContentProtectionConsentGranted(); + registerSettingsGlobalObserver(KEY_PACKAGE_VERIFIER_USER_CONSENT); + registerSettingsGlobalObserver(KEY_CONTENT_PROTECTION_USER_CONSENT); + readPackageVerifierConsentGranted(); + readContentProtectionUserConsentGranted(); } - /** - * Returns true if all the consents are granted - */ + /** Returns true if the consent is ultimately granted. */ public boolean isConsentGranted(@UserIdInt int userId) { - return mCachedPackageVerifierConsent - && mCachedContentProtectionConsent - && !isUserOrganizationManaged(userId); + return mCachedPackageVerifierConsent && isContentProtectionConsentGranted(userId); } + /** + * Not always cached internally and can be expensive, when possible prefer to use {@link + * #mCachedPackageVerifierConsent} instead. + */ private boolean isPackageVerifierConsentGranted() { - // Not always cached internally return Settings.Global.getInt( mContentResolver, KEY_PACKAGE_VERIFIER_USER_CONSENT, /* def= */ 0) >= 1; } - private boolean isContentProtectionConsentGranted() { - // Not always cached internally + /** + * Not always cached internally and can be expensive, when possible prefer to use {@link + * #mCachedContentProtectionUserConsent} instead. + */ + private boolean isContentProtectionUserConsentGranted() { return Settings.Global.getInt( mContentResolver, KEY_CONTENT_PROTECTION_USER_CONSENT, /* def= */ 0) >= 0; } + private void readPackageVerifierConsentGranted() { + mCachedPackageVerifierConsent = isPackageVerifierConsentGranted(); + } + + private void readContentProtectionUserConsentGranted() { + mCachedContentProtectionUserConsent = isContentProtectionUserConsentGranted(); + } + + /** Always cached internally, cheap and safe to use. */ private boolean isUserOrganizationManaged(@UserIdInt int userId) { - // Cached internally return mDevicePolicyManagerInternal.isUserOrganizationManaged(userId); } + /** Always cached internally, cheap and safe to use. */ + private boolean isContentProtectionPolicyGranted(@UserIdInt int userId) { + if (!manageDevicePolicyEnabled()) { + return false; + } + + @DevicePolicyManager.ContentProtectionPolicy + int policy = mDevicePolicyCache.getContentProtectionPolicy(userId); + + return switch (policy) { + case DevicePolicyManager.CONTENT_PROTECTION_ENABLED -> true; + case DevicePolicyManager.CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY -> + mCachedContentProtectionUserConsent; + default -> false; + }; + } + + /** Always cached internally, cheap and safe to use. */ + private boolean isContentProtectionConsentGranted(@UserIdInt int userId) { + if (!manageDevicePolicyEnabled()) { + return mCachedContentProtectionUserConsent && !isUserOrganizationManaged(userId); + } + + return isUserOrganizationManaged(userId) + ? isContentProtectionPolicyGranted(userId) + : mCachedContentProtectionUserConsent; + } + + private void registerSettingsGlobalObserver(@NonNull String key) { + registerSettingsObserver(Settings.Global.getUriFor(key)); + } + + private void registerSettingsObserver(@NonNull Uri uri) { + mContentResolver.registerContentObserver( + uri, /* notifyForDescendants= */ false, mContentObserver, UserHandle.USER_ALL); + } + private final class SettingsObserver extends ContentObserver { SettingsObserver(Handler handler) { @@ -108,17 +160,20 @@ public class ContentProtectionConsentManager { } @Override - public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) { + public void onChange(boolean selfChange, @Nullable Uri uri, @UserIdInt int userId) { + if (uri == null) { + return; + } final String property = uri.getLastPathSegment(); if (property == null) { return; } switch (property) { case KEY_PACKAGE_VERIFIER_USER_CONSENT: - mCachedPackageVerifierConsent = isPackageVerifierConsentGranted(); + readPackageVerifierConsentGranted(); return; case KEY_CONTENT_PROTECTION_USER_CONSENT: - mCachedContentProtectionConsent = isContentProtectionConsentGranted(); + readContentProtectionUserConsentGranted(); return; default: Slog.w(TAG, "Ignoring unexpected property: " + property); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 7d3af99b74d1..b8e09cce93b9 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -372,15 +372,6 @@ public final class ActiveServices { @Overridable public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L; - /** - * Disables foreground service background starts in System Alert Window for all types - * unless it already has a System Overlay Window. - */ - @ChangeId - @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM) - @Overridable - public static final long FGS_SAW_RESTRICTIONS = 319471980L; - final ActivityManagerService mAm; // Maximum number of services that we allow to start in the background @@ -8535,31 +8526,10 @@ public final class ActiveServices { } } - // The flag being enabled isn't enough to deny background start: we need to also check - // if there is a system alert UI present. if (ret == REASON_DENIED) { - // Flag check: are we disabling SAW FGS background starts? - final boolean shouldDisableSaw = Flags.fgsDisableSaw() - && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, callingUid); - if (shouldDisableSaw) { - final ProcessRecord processRecord = mAm - .getProcessRecordLocked(targetService.processName, - targetService.appInfo.uid); - if (processRecord != null) { - if (processRecord.mState.hasOverlayUi()) { - if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid, - callingPackage)) { - ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; - } - } - } else { - Slog.e(TAG, "Could not find process record for SAW check"); - } - } else { - if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid, - callingPackage)) { - ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; - } + if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid, + callingPackage)) { + ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e222878a5dd3..663ba8a38d77 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -9028,6 +9028,7 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid " + Process.myUid()); BinderProxy.dumpProxyDebugInfo(); + CriticalEventLog.getInstance().logExcessiveBinderCalls(uid); if (uid == Process.SYSTEM_UID) { Slog.i(TAG, "Skipping kill (uid is SYSTEM)"); } else { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 34ba7f0debb0..3abfe082db27 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1468,7 +1468,7 @@ class UserController implements Handler.Callback { // Send PROFILE_INACCESSIBLE broadcast if a profile was stopped final UserInfo userInfo = getUserInfo(userId); - if (userInfo.isProfile()) { + if (userInfo != null && userInfo.isProfile()) { UserInfo parent = mInjector.getUserManager().getProfileParent(userId); if (parent != null) { broadcastProfileAccessibleStateChanged(userId, parent.id, diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index e955b00566b8..c06bdf90a75a 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -23,13 +23,6 @@ flag { } flag { - name: "fgs_disable_saw" - namespace: "backstage_power" - description: "Disable System Alert Window FGS start" - bug: "296558535" -} - -flag { name: "bfgs_managed_network_access" namespace: "backstage_power" description: "Restrict network access for certain applications in BFGS process state" @@ -60,3 +53,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "backstage_power" + name: "defer_outgoing_bcasts" + description: "Defer outgoing broadcasts from processes in freezable state" + bug: "327496592" +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index 007b7462f637..fb826c824354 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -24,6 +24,7 @@ import android.app.SynchronousUserSwitchObserver; import android.app.TaskStackListener; import android.content.Context; import android.content.pm.UserInfo; +import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ComponentInfoInternal; @@ -39,6 +40,7 @@ import android.hardware.face.FaceEnrollOptions; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -64,6 +66,7 @@ import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; +import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; @@ -359,6 +362,17 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { null /* callback */); } + if (Build.isDebuggable()) { + BiometricUtils<Face> utils = FaceUtils.getInstance( + mFaceSensors.keyAt(0)); + for (UserInfo user : UserManager.get(mContext).getAliveUsers()) { + List<Face> enrollments = utils.getBiometricsForUser(mContext, user.id); + Slog.d(getTag(), "Expecting enrollments for user " + user.id + ": " + + enrollments.stream().map( + BiometricAuthenticator.Identifier::getBiometricId).toList()); + } + } + return mDaemon; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index a104cf4e1726..c04c47e2d95a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -27,6 +27,7 @@ import android.app.TaskStackListener; import android.content.Context; import android.content.pm.UserInfo; import android.content.res.TypedArray; +import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IInvalidationCallback; @@ -46,6 +47,7 @@ import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Binder; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -71,6 +73,7 @@ import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; +import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; @@ -382,6 +385,17 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi null /* callback */); } + if (Build.isDebuggable()) { + BiometricUtils<Fingerprint> utils = FingerprintUtils.getInstance( + mFingerprintSensors.keyAt(0)); + for (UserInfo user : UserManager.get(mContext).getAliveUsers()) { + List<Fingerprint> enrollments = utils.getBiometricsForUser(mContext, user.id); + Slog.d(getTag(), "Expecting enrollments for user " + user.id + ": " + + enrollments.stream().map( + BiometricAuthenticator.Identifier::getBiometricId).toList()); + } + } + return mDaemon; } diff --git a/services/core/java/com/android/server/criticalevents/CriticalEventLog.java b/services/core/java/com/android/server/criticalevents/CriticalEventLog.java index 816c3490d0a0..036284f53749 100644 --- a/services/core/java/com/android/server/criticalevents/CriticalEventLog.java +++ b/services/core/java/com/android/server/criticalevents/CriticalEventLog.java @@ -30,6 +30,7 @@ import com.android.server.criticalevents.nano.CriticalEventProto; import com.android.server.criticalevents.nano.CriticalEventProto.AppNotResponding; import com.android.server.criticalevents.nano.CriticalEventProto.HalfWatchdog; import com.android.server.criticalevents.nano.CriticalEventProto.InstallPackages; +import com.android.server.criticalevents.nano.CriticalEventProto.ExcessiveBinderCalls; import com.android.server.criticalevents.nano.CriticalEventProto.JavaCrash; import com.android.server.criticalevents.nano.CriticalEventProto.NativeCrash; import com.android.server.criticalevents.nano.CriticalEventProto.SystemServerStarted; @@ -143,6 +144,15 @@ public class CriticalEventLog { return System.currentTimeMillis(); } + /** Logs when a uid sends an excessive number of binder calls. */ + public void logExcessiveBinderCalls(int uid) { + CriticalEventProto event = new CriticalEventProto(); + ExcessiveBinderCalls calls = new ExcessiveBinderCalls(); + calls.uid = uid; + event.setExcessiveBinderCalls(calls); + log(event); + } + /** Logs when one or more packages are installed. */ public void logInstallPackagesStarted() { CriticalEventProto event = new CriticalEventProto(); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 23ca81468294..76f303596bdb 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1725,6 +1725,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mTempBrightnessEvent.setBrightness(brightnessState); mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId); mTempBrightnessEvent.setDisplayState(state); + mTempBrightnessEvent.setDisplayPolicy(mPowerRequest.policy); mTempBrightnessEvent.setReason(mBrightnessReason); mTempBrightnessEvent.setHbmMax(mBrightnessRangeController.getCurrentBrightnessMax()); mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode()); diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java index 5423b74711a2..82b401a7cc83 100644 --- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java +++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java @@ -16,6 +16,9 @@ package com.android.server.display.brightness; +import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF; +import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.policyToString; + import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT; import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessModeToString; @@ -46,6 +49,7 @@ public final class BrightnessEvent { private int mDisplayId; private String mPhysicalDisplayId; private int mDisplayState; + private int mDisplayPolicy; private long mTime; private float mLux; private float mPreThresholdLux; @@ -85,6 +89,7 @@ public final class BrightnessEvent { mDisplayId = that.getDisplayId(); mPhysicalDisplayId = that.getPhysicalDisplayId(); mDisplayState = that.mDisplayState; + mDisplayPolicy = that.mDisplayPolicy; mTime = that.getTime(); // Lux values mLux = that.getLux(); @@ -117,6 +122,7 @@ public final class BrightnessEvent { mTime = SystemClock.uptimeMillis(); mPhysicalDisplayId = ""; mDisplayState = Display.STATE_UNKNOWN; + mDisplayPolicy = POLICY_OFF; // Lux values mLux = 0; mPreThresholdLux = 0; @@ -155,6 +161,7 @@ public final class BrightnessEvent { && mDisplayId == that.mDisplayId && mPhysicalDisplayId.equals(that.mPhysicalDisplayId) && mDisplayState == that.mDisplayState + && mDisplayPolicy == that.mDisplayPolicy && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux) && Float.floatToRawIntBits(mPreThresholdLux) == Float.floatToRawIntBits(that.mPreThresholdLux) @@ -191,6 +198,7 @@ public final class BrightnessEvent { + "disp=" + mDisplayId + ", physDisp=" + mPhysicalDisplayId + ", displayState=" + Display.stateToString(mDisplayState) + + ", displayPolicy=" + policyToString(mDisplayPolicy) + ", brt=" + mBrightness + ((mFlags & FLAG_USER_SET) != 0 ? "(user_set)" : "") + ", initBrt=" + mInitialBrightness + ", rcmdBrt=" + mRecommendedBrightness @@ -251,6 +259,10 @@ public final class BrightnessEvent { mDisplayState = state; } + public void setDisplayPolicy(int policy) { + mDisplayPolicy = policy; + } + public float getLux() { return mLux; } diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java index a597edd93515..8fdc22b81769 100644 --- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java @@ -59,7 +59,7 @@ public class ProxyLocationProvider extends AbstractLocationProvider implements private static final String EXTRA_LOCATION_TAGS = "android:location_allow_listed_tags"; private static final String LOCATION_TAGS_SEPARATOR = ";"; - private static final long RESET_DELAY_MS = 1000; + private static final long RESET_DELAY_MS = 10000; /** * Creates and registers this proxy. If no suitable service is available for the proxy, returns diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 9a76ebd148aa..29ea0713e0a8 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -108,7 +108,6 @@ import android.provider.DeviceConfig; import android.provider.Settings; import android.security.AndroidKeyStoreMaintenance; import android.security.Authorization; -import android.security.KeyStore; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import android.security.keystore.recovery.KeyChainProtectionParams; @@ -169,6 +168,7 @@ import java.io.PrintWriter; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -292,7 +292,7 @@ public class LockSettingsService extends ILockSettings.Stub { private final IActivityManager mActivityManager; private final SyntheticPasswordManager mSpManager; - private final java.security.KeyStore mJavaKeyStore; + private final KeyStore mKeyStore; private final RecoverableKeyStoreManager mRecoverableKeyStoreManager; private final UnifiedProfilePasswordCache mUnifiedProfilePasswordCache; @@ -564,10 +564,6 @@ public class LockSettingsService extends ILockSettings.Stub { return DeviceStateCache.getInstance(); } - public KeyStore getKeyStore() { - return KeyStore.getInstance(); - } - public RecoverableKeyStoreManager getRecoverableKeyStoreManager() { return RecoverableKeyStoreManager.getInstance(mContext); } @@ -619,9 +615,9 @@ public class LockSettingsService extends ILockSettings.Stub { return (BiometricManager) mContext.getSystemService(Context.BIOMETRIC_SERVICE); } - public java.security.KeyStore getJavaKeyStore() { + public KeyStore getKeyStore() { try { - java.security.KeyStore ks = java.security.KeyStore.getInstance( + KeyStore ks = KeyStore.getInstance( SyntheticPasswordCrypto.androidKeystoreProviderName()); ks.load(new AndroidKeyStoreLoadStoreParameter( SyntheticPasswordCrypto.keyNamespace())); @@ -631,8 +627,7 @@ public class LockSettingsService extends ILockSettings.Stub { } } - public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache( - java.security.KeyStore ks) { + public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(KeyStore ks) { return new UnifiedProfilePasswordCache(ks); } @@ -654,7 +649,7 @@ public class LockSettingsService extends ILockSettings.Stub { protected LockSettingsService(Injector injector) { mInjector = injector; mContext = injector.getContext(); - mJavaKeyStore = injector.getJavaKeyStore(); + mKeyStore = injector.getKeyStore(); mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager(); mHandler = injector.getHandler(injector.getServiceThread()); mStrongAuth = injector.getStrongAuth(); @@ -676,7 +671,7 @@ public class LockSettingsService extends ILockSettings.Stub { mGatekeeperPasswords = new LongSparseArray<>(); mSpManager = injector.getSyntheticPasswordManager(mStorage); - mUnifiedProfilePasswordCache = injector.getUnifiedProfilePasswordCache(mJavaKeyStore); + mUnifiedProfilePasswordCache = injector.getUnifiedProfilePasswordCache(mKeyStore); mBiometricDeferredQueue = new BiometricDeferredQueue(mSpManager, mHandler); mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(), @@ -1482,7 +1477,7 @@ public class LockSettingsService extends ILockSettings.Stub { byte[] encryptedPassword = Arrays.copyOfRange(storedData, PROFILE_KEY_IV_SIZE, storedData.length); byte[] decryptionResult; - SecretKey decryptionKey = (SecretKey) mJavaKeyStore.getKey( + SecretKey decryptionKey = (SecretKey) mKeyStore.getKey( PROFILE_KEY_NAME_DECRYPT + userId, null); Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" @@ -1875,9 +1870,10 @@ public class LockSettingsService extends ILockSettings.Stub { } } - private void onPostPasswordChanged(LockscreenCredential newCredential, int userHandle) { - updatePasswordHistory(newCredential, userHandle); - mContext.getSystemService(TrustManager.class).reportEnabledTrustAgentsChanged(userHandle); + private void onPostPasswordChanged(LockscreenCredential newCredential, int userId) { + updatePasswordHistory(newCredential, userId); + mContext.getSystemService(TrustManager.class).reportEnabledTrustAgentsChanged(userId); + sendMainUserCredentialChangedNotificationIfNeeded(userId); } /** @@ -2076,16 +2072,16 @@ public class LockSettingsService extends ILockSettings.Stub { keyGenerator.init(new SecureRandom()); SecretKey secretKey = keyGenerator.generateKey(); try { - mJavaKeyStore.setEntry( + mKeyStore.setEntry( PROFILE_KEY_NAME_ENCRYPT + profileUserId, - new java.security.KeyStore.SecretKeyEntry(secretKey), + new KeyStore.SecretKeyEntry(secretKey), new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); - mJavaKeyStore.setEntry( + mKeyStore.setEntry( PROFILE_KEY_NAME_DECRYPT + profileUserId, - new java.security.KeyStore.SecretKeyEntry(secretKey), + new KeyStore.SecretKeyEntry(secretKey), new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) @@ -2094,7 +2090,7 @@ public class LockSettingsService extends ILockSettings.Stub { .setUserAuthenticationValidityDurationSeconds(30) .build()); // Key imported, obtain a reference to it. - SecretKey keyStoreEncryptionKey = (SecretKey) mJavaKeyStore.getKey( + SecretKey keyStoreEncryptionKey = (SecretKey) mKeyStore.getKey( PROFILE_KEY_NAME_ENCRYPT + profileUserId, null); Cipher cipher = Cipher.getInstance( KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" @@ -2104,7 +2100,7 @@ public class LockSettingsService extends ILockSettings.Stub { iv = cipher.getIV(); } finally { // The original key can now be discarded. - mJavaKeyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + profileUserId); + mKeyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + profileUserId); } } catch (UnrecoverableKeyException | BadPaddingException | IllegalBlockSizeException | KeyStoreException @@ -2556,11 +2552,10 @@ public class LockSettingsService extends ILockSettings.Stub { final String encryptAlias = PROFILE_KEY_NAME_ENCRYPT + targetUserId; final String decryptAlias = PROFILE_KEY_NAME_DECRYPT + targetUserId; try { - if (mJavaKeyStore.containsAlias(encryptAlias) || - mJavaKeyStore.containsAlias(decryptAlias)) { + if (mKeyStore.containsAlias(encryptAlias) || mKeyStore.containsAlias(decryptAlias)) { Slogf.i(TAG, "Removing keystore profile key for user %d", targetUserId); - mJavaKeyStore.deleteEntry(encryptAlias); - mJavaKeyStore.deleteEntry(decryptAlias); + mKeyStore.deleteEntry(encryptAlias); + mKeyStore.deleteEntry(decryptAlias); } } catch (KeyStoreException e) { // We have tried our best to remove the key. @@ -3062,7 +3057,6 @@ public class LockSettingsService extends ILockSettings.Stub { setCurrentLskfBasedProtectorId(newProtectorId, userId); LockPatternUtils.invalidateCredentialTypeCache(); synchronizeUnifiedChallengeForProfiles(userId, profilePasswords); - sendMainUserCredentialChangedNotificationIfNeeded(userId); setUserPasswordMetrics(credential, userId); mUnifiedProfilePasswordCache.removePassword(userId); @@ -3457,7 +3451,7 @@ public class LockSettingsService extends ILockSettings.Stub { private void dumpKeystoreKeys(IndentingPrintWriter pw) { try { - final Enumeration<String> aliases = mJavaKeyStore.aliases(); + final Enumeration<String> aliases = mKeyStore.aliases(); while (aliases.hasMoreElements()) { pw.println(aliases.nextElement()); } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 9e98a5809650..09c6dc0e603c 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -128,6 +128,22 @@ public class MediaSessionService extends SystemService implements Monitor { */ private static final String MEDIA_BUTTON_RECEIVER = "media_button_receiver"; + /** + * Action reported to UsageStatsManager when a media session becomes active and user engaged + * for a given app. App is expected to show an ongoing notification after this. + */ + private static final String USAGE_STATS_ACTION_START = "start"; + + /** + * Action reported to UsageStatsManager when a media session is no longer active and user + * engaged for a given app. If media session only pauses for a brief time the event will not + * necessarily be reported in case user is still "engaging" and will restart it momentarily. + * In such case, action may be reported after a short delay to ensure user is truly no longer + * engaging. Afterwards, the app is no longer expected to show an ongoing notification. + */ + private static final String USAGE_STATS_ACTION_STOP = "stop"; + private static final String USAGE_STATS_CATEGORY = "android.media"; + private final Context mContext; private final SessionManagerImpl mSessionManagerImpl; private final MessageHandler mHandler = new MessageHandler(); @@ -639,13 +655,15 @@ public class MediaSessionService extends SystemService implements Monitor { if (userEngaged) { if (!mUserEngagingSessions.contains(sessionUid)) { mUserEngagingSessions.put(sessionUid, new HashSet<>()); - reportUserInteractionEvent(/* action= */ "start", record.getUserId(), packageName); + reportUserInteractionEvent( + USAGE_STATS_ACTION_START, record.getUserId(), packageName); } mUserEngagingSessions.get(sessionUid).add(token); } else if (mUserEngagingSessions.contains(sessionUid)) { mUserEngagingSessions.get(sessionUid).remove(token); if (mUserEngagingSessions.get(sessionUid).isEmpty()) { - reportUserInteractionEvent(/* action= */ "stop", record.getUserId(), packageName); + reportUserInteractionEvent( + USAGE_STATS_ACTION_STOP, record.getUserId(), packageName); mUserEngagingSessions.remove(sessionUid); } } @@ -653,7 +671,7 @@ public class MediaSessionService extends SystemService implements Monitor { private void reportUserInteractionEvent(String action, int userId, String packageName) { PersistableBundle extras = new PersistableBundle(); - extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, "android.media"); + extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, USAGE_STATS_CATEGORY); extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, action); mUsageStatsManagerInternal.reportUserInteractionEvent(packageName, userId, extras); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 428fca082f75..e9a7fe1371ac 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -6410,7 +6410,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private boolean performHapticFeedback(int effectId, boolean always, String reason) { return performHapticFeedback(Process.myUid(), mContext.getOpPackageName(), - effectId, always, reason); + effectId, always, reason, false /* fromIme */); } @Override @@ -6420,7 +6420,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public boolean performHapticFeedback(int uid, String packageName, int effectId, - boolean always, String reason) { + boolean always, String reason, boolean fromIme) { if (!mVibrator.hasVibrator()) { return false; } @@ -6431,7 +6431,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } VibrationAttributes attrs = mHapticFeedbackVibrationProvider.getVibrationAttributesForHapticFeedback( - effectId, /* bypassVibrationIntensitySetting= */ always); + effectId, /* bypassVibrationIntensitySetting= */ always, fromIme); VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, effectId); mVibrator.vibrate(uid, packageName, effect, reason, attrs); return true; diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 2174fd62ea00..5956594acd26 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -1072,7 +1072,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * Call from application to perform haptic feedback on its window. */ public boolean performHapticFeedback(int uid, String packageName, int effectId, - boolean always, String reason); + boolean always, String reason, boolean fromIme); /** * Called when we have started keeping the screen on because a window diff --git a/services/core/java/com/android/server/vibrator/GroupedAggregatedLogRecords.java b/services/core/java/com/android/server/vibrator/GroupedAggregatedLogRecords.java new file mode 100644 index 000000000000..7ee29016a58b --- /dev/null +++ b/services/core/java/com/android/server/vibrator/GroupedAggregatedLogRecords.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2024 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.vibrator; + +import android.os.SystemClock; +import android.util.IndentingPrintWriter; +import android.util.SparseArray; +import android.util.proto.ProtoOutputStream; + +import java.util.ArrayDeque; + +/** + * A generic grouped list of aggregated log records to be printed in dumpsys. + * + * <p>This can be used to dump history of operations or requests to the vibrator services, e.g. + * vibration requests grouped by usage or vibration parameters sent to the vibrator control service. + * + * @param <T> The type of log entries aggregated in this record. + */ +abstract class GroupedAggregatedLogRecords<T extends GroupedAggregatedLogRecords.SingleLogRecord> { + private final SparseArray<ArrayDeque<AggregatedLogRecord<T>>> mGroupedRecords; + private final int mSizeLimit; + private final int mAggregationTimeLimitMs; + + GroupedAggregatedLogRecords(int sizeLimit, int aggregationTimeLimitMs) { + mGroupedRecords = new SparseArray<>(); + mSizeLimit = sizeLimit; + mAggregationTimeLimitMs = aggregationTimeLimitMs; + } + + /** Prints a header to identify the group to be logged. */ + abstract void dumpGroupHeader(IndentingPrintWriter pw, int groupKey); + + /** Returns the {@link ProtoOutputStream} repeated field id to log records of this group. */ + abstract long findGroupKeyProtoFieldId(int groupKey); + + /** + * Adds given entry to this record list, dropping the oldest record if size limit was reached + * for its group. + * + * @param record The new {@link SingleLogRecord} to be recorded. + * @return The oldest {@link AggregatedLogRecord} entry being dropped from the group list if + * it's full, null otherwise. + */ + final synchronized AggregatedLogRecord<T> add(T record) { + int groupKey = record.getGroupKey(); + if (!mGroupedRecords.contains(groupKey)) { + mGroupedRecords.put(groupKey, new ArrayDeque<>(mSizeLimit)); + } + ArrayDeque<AggregatedLogRecord<T>> records = mGroupedRecords.get(groupKey); + if (mAggregationTimeLimitMs > 0 && !records.isEmpty()) { + AggregatedLogRecord<T> lastAggregatedRecord = records.getLast(); + if (lastAggregatedRecord.mayAggregate(record, mAggregationTimeLimitMs)) { + lastAggregatedRecord.record(record); + return null; + } + } + AggregatedLogRecord<T> removedRecord = null; + if (records.size() >= mSizeLimit) { + removedRecord = records.removeFirst(); + } + records.addLast(new AggregatedLogRecord<>(record)); + return removedRecord; + } + + final synchronized void dump(IndentingPrintWriter pw) { + for (int i = 0; i < mGroupedRecords.size(); i++) { + dumpGroupHeader(pw, mGroupedRecords.keyAt(i)); + pw.increaseIndent(); + for (AggregatedLogRecord<T> records : mGroupedRecords.valueAt(i)) { + records.dump(pw); + } + pw.decreaseIndent(); + pw.println(); + } + } + + final synchronized void dump(ProtoOutputStream proto) { + for (int i = 0; i < mGroupedRecords.size(); i++) { + long fieldId = findGroupKeyProtoFieldId(mGroupedRecords.keyAt(i)); + for (AggregatedLogRecord<T> records : mGroupedRecords.valueAt(i)) { + records.dump(proto, fieldId); + } + } + } + + /** + * Represents an aggregation of log record entries that can be printed in a compact manner. + * + * <p>The aggregation is controlled by a time limit on the difference between the creation time + * of two consecutive entries that {@link SingleLogRecord#mayAggregate}. + * + * @param <T> The type of log entries aggregated in this record. + */ + static final class AggregatedLogRecord<T extends SingleLogRecord> { + private final T mFirst; + private T mLatest; + private int mCount; + + AggregatedLogRecord(T record) { + mLatest = mFirst = record; + mCount = 1; + } + + T getLatest() { + return mLatest; + } + + synchronized boolean mayAggregate(T record, long timeLimitMs) { + long timeDeltaMs = Math.abs(mLatest.getCreateUptimeMs() - record.getCreateUptimeMs()); + return mLatest.mayAggregate(record) && timeDeltaMs < timeLimitMs; + } + + synchronized void record(T record) { + mLatest = record; + mCount++; + } + + synchronized void dump(IndentingPrintWriter pw) { + mFirst.dump(pw); + if (mCount == 1) { + return; + } + if (mCount > 2) { + pw.println("-> Skipping " + (mCount - 2) + " aggregated entries, latest:"); + } + mLatest.dump(pw); + } + + synchronized void dump(ProtoOutputStream proto, long fieldId) { + mFirst.dump(proto, fieldId); + if (mCount > 1) { + mLatest.dump(proto, fieldId); + } + } + } + + /** + * Represents a single log entry that can be grouped and aggregated for compact logging. + * + * <p>Entries are first grouped by an integer group key, and then aggregated with consecutive + * entries of same group within a limited timespan. + */ + interface SingleLogRecord { + + /** The group identifier for this record (e.g. vibration usage). */ + int getGroupKey(); + + /** + * The timestamp in millis that should be used for aggregation of close entries. + * + * <p>Should be {@link SystemClock#uptimeMillis()} to be used for calculations. + */ + long getCreateUptimeMs(); + + /** + * Returns true if this record can be aggregated with the given one (e.g. the represent the + * same vibration request from the same process client). + */ + boolean mayAggregate(SingleLogRecord record); + + /** Writes this record into given {@link IndentingPrintWriter}. */ + void dump(IndentingPrintWriter pw); + + /** Writes this record into given {@link ProtoOutputStream} field. */ + void dump(ProtoOutputStream proto, long fieldId); + } +} diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java index 70e2e27a3bae..8f755f4ecec8 100644 --- a/services/core/java/com/android/server/vibrator/HalVibration.java +++ b/services/core/java/com/android/server/vibrator/HalVibration.java @@ -54,12 +54,18 @@ final class HalVibration extends Vibration { /** Vibration status. */ private Vibration.Status mStatus; + /** Reported scale values applied to the vibration effects. */ + private int mScaleLevel; + private float mAdaptiveScale; + HalVibration(@NonNull IBinder token, @NonNull CombinedVibration effect, @NonNull CallerInfo callerInfo) { super(token, callerInfo); mOriginalEffect = effect; mEffectToPlay = effect; mStatus = Vibration.Status.RUNNING; + mScaleLevel = VibrationScaler.SCALE_NONE; + mAdaptiveScale = VibrationScaler.ADAPTIVE_SCALE_NONE; } /** @@ -119,20 +125,24 @@ final class HalVibration extends Vibration { } /** - * Scales the {@link #getEffectToPlay()} and each fallback effect with a scaling transformation. - * - * @param scaler A {@link VibrationEffect.Transformation<Integer>} that takes one of the - * {@code VibrationAttributes.USAGE_*} as the modifier to scale the effect - * based on the user settings. + * Scales the {@link #getEffectToPlay()} and each fallback effect based on the vibration usage. */ - public void scaleEffects(VibrationEffect.Transformation<Integer> scaler) { + public void scaleEffects(VibrationScaler scaler) { int vibrationUsage = callerInfo.attrs.getUsage(); - CombinedVibration newEffect = mEffectToPlay.transform(scaler, vibrationUsage); + + // Save scale values for debugging purposes. + mScaleLevel = scaler.getScaleLevel(vibrationUsage); + mAdaptiveScale = scaler.getAdaptiveHapticsScale(vibrationUsage); + + // Scale all VibrationEffect instances in given CombinedVibration. + CombinedVibration newEffect = mEffectToPlay.transform(scaler::scale, vibrationUsage); if (!Objects.equals(mEffectToPlay, newEffect)) { mEffectToPlay = newEffect; } + + // Scale all fallback VibrationEffect instances that can be used by VibrationThread. for (int i = 0; i < mFallbacks.size(); i++) { - mFallbacks.setValueAt(i, scaler.transform(mFallbacks.valueAt(i), vibrationUsage)); + mFallbacks.setValueAt(i, scaler.scale(mFallbacks.valueAt(i), vibrationUsage)); } } @@ -171,7 +181,7 @@ final class HalVibration extends Vibration { CombinedVibration originalEffect = Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect; return new Vibration.DebugInfo(mStatus, stats, mEffectToPlay, originalEffect, - /* scale= */ 0, callerInfo); + mScaleLevel, mAdaptiveScale, callerInfo); } /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */ diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java index a34621642bcd..9756094e5af0 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java @@ -19,8 +19,8 @@ package com.android.server.vibrator; import android.annotation.Nullable; import android.content.res.Resources; import android.os.VibrationEffect; -import android.os.vibrator.Flags; import android.os.VibratorInfo; +import android.os.vibrator.Flags; import android.os.vibrator.persistence.ParsedVibration; import android.os.vibrator.persistence.VibrationXmlParser; import android.text.TextUtils; @@ -73,8 +73,6 @@ import java.io.IOException; * * <p>After a successful parsing of the customization XML file, it returns a {@link SparseArray} * that maps each customized haptic feedback effect ID to its respective {@link VibrationEffect}. - * - * @hide */ final class HapticFeedbackCustomization { private static final String TAG = "HapticFeedbackCustomization"; @@ -104,8 +102,6 @@ final class HapticFeedbackCustomization { * @throws {@link IOException} if an IO error occurs while parsing the customization XML. * @throws {@link CustomizationParserException} for any non-IO error that occurs when parsing * the XML, like an invalid XML content or an invalid haptic feedback constant. - * - * @hide */ @Nullable static SparseArray<VibrationEffect> loadVibrations(Resources res, VibratorInfo vibratorInfo) @@ -202,8 +198,6 @@ final class HapticFeedbackCustomization { /** * Represents an error while parsing a haptic feedback customization XML. - * - * @hide */ static final class CustomizationParserException extends Exception { private CustomizationParserException(String message) { diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java index 519acec2f7b4..96f045d7e258 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java @@ -34,8 +34,6 @@ import java.io.PrintWriter; /** * Provides the {@link VibrationEffect} and {@link VibrationAttributes} for haptic feedback. - * - * @hide */ public final class HapticFeedbackVibrationProvider { private static final String TAG = "HapticFeedbackVibrationProvider"; @@ -58,17 +56,14 @@ public final class HapticFeedbackVibrationProvider { private float mKeyboardVibrationFixedAmplitude; - /** @hide */ public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) { this(res, vibrator.getInfo()); } - /** @hide */ public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) { this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo)); } - /** @hide */ @VisibleForTesting HapticFeedbackVibrationProvider( Resources res, VibratorInfo vibratorInfo, @@ -190,10 +185,11 @@ public final class HapticFeedbackVibrationProvider { * to get. * @param bypassVibrationIntensitySetting {@code true} if the returned attribute should bypass * vibration intensity settings. {@code false} otherwise. + * @param fromIme the haptic feedback is performed from an IME. * @return the {@link VibrationAttributes} that should be used for the provided haptic feedback. */ public VibrationAttributes getVibrationAttributesForHapticFeedback( - int effectId, boolean bypassVibrationIntensitySetting) { + int effectId, boolean bypassVibrationIntensitySetting, boolean fromIme) { VibrationAttributes attrs; switch (effectId) { case HapticFeedbackConstants.EDGE_SQUEEZE: @@ -209,7 +205,7 @@ public final class HapticFeedbackVibrationProvider { break; case HapticFeedbackConstants.KEYBOARD_TAP: case HapticFeedbackConstants.KEYBOARD_RELEASE: - attrs = createKeyboardVibrationAttributes(); + attrs = createKeyboardVibrationAttributes(fromIme); break; default: attrs = TOUCH_VIBRATION_ATTRIBUTES; @@ -222,7 +218,7 @@ public final class HapticFeedbackVibrationProvider { if (shouldBypassInterruptionPolicy(effectId)) { flags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; } - if (shouldBypassIntensityScale(effectId)) { + if (shouldBypassIntensityScale(effectId, fromIme)) { flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE; } @@ -337,9 +333,9 @@ public final class HapticFeedbackVibrationProvider { /* fallbackForPredefinedEffect= */ predefinedEffectFallback); } - private boolean shouldBypassIntensityScale(int effectId) { - if (!Flags.keyboardCategoryEnabled() || mKeyboardVibrationFixedAmplitude < 0) { - // shouldn't bypass if not support keyboard category or no fixed amplitude + private boolean shouldBypassIntensityScale(int effectId, boolean isIme) { + if (!Flags.keyboardCategoryEnabled() || mKeyboardVibrationFixedAmplitude < 0 || !isIme) { + // Shouldn't bypass if not support keyboard category, no fixed amplitude or not an IME. return false; } switch (effectId) { @@ -353,8 +349,9 @@ public final class HapticFeedbackVibrationProvider { return false; } - private static VibrationAttributes createKeyboardVibrationAttributes() { - if (!Flags.keyboardCategoryEnabled()) { + private VibrationAttributes createKeyboardVibrationAttributes(boolean fromIme) { + // Use touch attribute when the keyboard category is disable or it's not from an IME. + if (!Flags.keyboardCategoryEnabled() || !fromIme) { return TOUCH_VIBRATION_ATTRIBUTES; } diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index b2e808ac8e95..b490f57a936e 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -218,12 +218,13 @@ abstract class Vibration { private final long mDurationMs; @Nullable private final CombinedVibration mOriginalEffect; - private final float mScale; + private final int mScaleLevel; + private final float mAdaptiveScale; private final Status mStatus; DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect, - @Nullable CombinedVibration originalEffect, float scale, - @NonNull CallerInfo callerInfo) { + @Nullable CombinedVibration originalEffect, int scaleLevel, + float adaptiveScale, @NonNull CallerInfo callerInfo) { Objects.requireNonNull(callerInfo); mCreateTime = stats.getCreateTimeDebug(); mStartTime = stats.getStartTimeDebug(); @@ -231,7 +232,8 @@ abstract class Vibration { mDurationMs = stats.getDurationDebug(); mPlayedEffect = playedEffect; mOriginalEffect = originalEffect; - mScale = scale; + mScaleLevel = scaleLevel; + mAdaptiveScale = adaptiveScale; mCallerInfo = callerInfo; mStatus = status; } @@ -246,7 +248,8 @@ abstract class Vibration { + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) + ", playedEffect: " + mPlayedEffect + ", originalEffect: " + mOriginalEffect - + ", scale: " + String.format(Locale.ROOT, "%.2f", mScale) + + ", scaleLevel: " + VibrationScaler.scaleLevelToString(mScaleLevel) + + ", adaptiveScale: " + String.format(Locale.ROOT, "%.2f", mAdaptiveScale) + ", callerInfo: " + mCallerInfo; } @@ -259,26 +262,39 @@ abstract class Vibration { void dumpCompact(IndentingPrintWriter pw) { boolean isExternalVibration = mPlayedEffect == null; String timingsStr = String.format(Locale.ROOT, - "%s | %8s | %20s | duration: %5dms | start: %12s | end: %10s", + "%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s", DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)), isExternalVibration ? "external" : "effect", mStatus.name().toLowerCase(Locale.ROOT), mDurationMs, mStartTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mStartTime)), mEndTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mEndTime))); - String callerInfoStr = String.format(Locale.ROOT, - " | %s (uid=%d, deviceId=%d) | usage: %s (audio=%s) | flags: %s | reason: %s", - mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, - mCallerInfo.attrs.usageToString(), - AudioAttributes.usageToString(mCallerInfo.attrs.getAudioUsage()), + String paramStr = String.format(Locale.ROOT, + " | scale: %8s (adaptive=%.2f) | flags: %4s | usage: %s", + VibrationScaler.scaleLevelToString(mScaleLevel), mAdaptiveScale, Long.toBinaryString(mCallerInfo.attrs.getFlags()), - mCallerInfo.reason); + mCallerInfo.attrs.usageToString()); + // Optional, most vibrations have category unknown so skip them to simplify the logs + String categoryStr = + mCallerInfo.attrs.getCategory() != VibrationAttributes.CATEGORY_UNKNOWN + ? " | category=" + VibrationAttributes.categoryToString( + mCallerInfo.attrs.getCategory()) + : ""; + // Optional, most vibrations should not be defined via AudioAttributes + // so skip them to simplify the logs + String audioUsageStr = + mCallerInfo.attrs.getOriginalAudioUsage() != AudioAttributes.USAGE_UNKNOWN + ? " | audioUsage=" + AudioAttributes.usageToString( + mCallerInfo.attrs.getOriginalAudioUsage()) + : ""; + String callerStr = String.format(Locale.ROOT, + " | %s (uid=%d, deviceId=%d) | reason: %s", + mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason); String effectStr = String.format(Locale.ROOT, - " | played: %s | original: %s | scale: %.2f", + " | played: %s | original: %s", mPlayedEffect == null ? null : mPlayedEffect.toDebugString(), - mOriginalEffect == null ? null : mOriginalEffect.toDebugString(), - mScale); - pw.println(timingsStr + callerInfoStr + effectStr); + mOriginalEffect == null ? null : mOriginalEffect.toDebugString()); + pw.println(timingsStr + paramStr + categoryStr + audioUsageStr + callerStr + effectStr); } /** Write this info into given {@link PrintWriter}. */ @@ -293,7 +309,8 @@ abstract class Vibration { + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime)))); pw.println("playedEffect = " + mPlayedEffect); pw.println("originalEffect = " + mOriginalEffect); - pw.println("scale = " + String.format(Locale.ROOT, "%.2f", mScale)); + pw.println("scale = " + VibrationScaler.scaleLevelToString(mScaleLevel)); + pw.println("adaptiveScale = " + String.format(Locale.ROOT, "%.2f", mAdaptiveScale)); pw.println("callerInfo = " + mCallerInfo); pw.decreaseIndent(); } @@ -310,6 +327,7 @@ abstract class Vibration { final VibrationAttributes attrs = mCallerInfo.attrs; proto.write(VibrationAttributesProto.USAGE, attrs.getUsage()); proto.write(VibrationAttributesProto.AUDIO_USAGE, attrs.getAudioUsage()); + proto.write(VibrationAttributesProto.CATEGORY, attrs.getCategory()); proto.write(VibrationAttributesProto.FLAGS, attrs.getFlags()); proto.end(attrsToken); diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java index 5d17884c769b..d9ca71003aae 100644 --- a/services/core/java/com/android/server/vibrator/VibrationScaler.java +++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java @@ -26,10 +26,14 @@ import android.os.Vibrator; import android.os.vibrator.Flags; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.VibrationEffectSegment; +import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; +import android.util.proto.ProtoOutputStream; +import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Locale; /** Controls vibration scaling. */ final class VibrationScaler { @@ -37,13 +41,12 @@ final class VibrationScaler { // Scale levels. Each level, except MUTE, is defined as the delta between the current setting // and the default intensity for that type of vibration (i.e. current - default). - private static final int SCALE_VERY_LOW = - ExternalVibrationScale.ScaleLevel.SCALE_VERY_LOW; // -2 - private static final int SCALE_LOW = ExternalVibrationScale.ScaleLevel.SCALE_LOW; // -1 - private static final int SCALE_NONE = ExternalVibrationScale.ScaleLevel.SCALE_NONE; // 0 - private static final int SCALE_HIGH = ExternalVibrationScale.ScaleLevel.SCALE_HIGH; // 1 - private static final int SCALE_VERY_HIGH = - ExternalVibrationScale.ScaleLevel.SCALE_VERY_HIGH; // 2 + static final int SCALE_VERY_LOW = ExternalVibrationScale.ScaleLevel.SCALE_VERY_LOW; // -2 + static final int SCALE_LOW = ExternalVibrationScale.ScaleLevel.SCALE_LOW; // -1 + static final int SCALE_NONE = ExternalVibrationScale.ScaleLevel.SCALE_NONE; // 0 + static final int SCALE_HIGH = ExternalVibrationScale.ScaleLevel.SCALE_HIGH; // 1 + static final int SCALE_VERY_HIGH = ExternalVibrationScale.ScaleLevel.SCALE_VERY_HIGH; // 2 + static final float ADAPTIVE_SCALE_NONE = 1f; // Scale factors for each level. private static final float SCALE_FACTOR_VERY_LOW = 0.6f; @@ -52,6 +55,8 @@ final class VibrationScaler { private static final float SCALE_FACTOR_HIGH = 1.2f; private static final float SCALE_FACTOR_VERY_HIGH = 1.4f; + private static final ScaleLevel SCALE_LEVEL_NONE = new ScaleLevel(SCALE_FACTOR_NONE); + // A mapping from the intensity adjustment to the scaling to apply, where the intensity // adjustment is defined as the delta between the default intensity level and the user selected // intensity level. It's important that we apply the scaling on the delta between the two so @@ -69,7 +74,7 @@ final class VibrationScaler { mScaleLevels = new SparseArray<>(); mScaleLevels.put(SCALE_VERY_LOW, new ScaleLevel(SCALE_FACTOR_VERY_LOW)); mScaleLevels.put(SCALE_LOW, new ScaleLevel(SCALE_FACTOR_LOW)); - mScaleLevels.put(SCALE_NONE, new ScaleLevel(SCALE_FACTOR_NONE)); + mScaleLevels.put(SCALE_NONE, SCALE_LEVEL_NONE); mScaleLevels.put(SCALE_HIGH, new ScaleLevel(SCALE_FACTOR_HIGH)); mScaleLevels.put(SCALE_VERY_HIGH, new ScaleLevel(SCALE_FACTOR_VERY_HIGH)); } @@ -87,25 +92,24 @@ final class VibrationScaler { * @param usageHint one of VibrationAttributes.USAGE_* * @return one of ExternalVibrationScale.ScaleLevel.SCALE_* */ - public int getExternalVibrationScaleLevel(int usageHint) { + public int getScaleLevel(int usageHint) { int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint); int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); - if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) { // Bypassing user settings, or it has changed between checking and scaling. Use default. return SCALE_NONE; } int scaleLevel = currentIntensity - defaultIntensity; - if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) { return scaleLevel; - } else { - // Something about our scaling has gone wrong, so just play with no scaling. - Slog.w(TAG, "Error in scaling calculations, ended up with invalid scale level " - + scaleLevel + " for vibration with usage " + usageHint); - return SCALE_NONE; } + + // Something about our scaling has gone wrong, so just play with no scaling. + Slog.wtf(TAG, "Error in scaling calculations, ended up with invalid scale level " + + scaleLevel + " for vibration with usage " + usageHint); + + return SCALE_NONE; } /** @@ -117,11 +121,9 @@ final class VibrationScaler { * @return The adaptive haptics scale. */ public float getAdaptiveHapticsScale(int usageHint) { - if (shouldApplyAdaptiveHapticsScale(usageHint)) { - return mAdaptiveHapticsScales.get(usageHint); - } - - return 1f; // no scaling + return Flags.adaptiveHapticsEnabled() + ? mAdaptiveHapticsScales.get(usageHint, ADAPTIVE_SCALE_NONE) + : ADAPTIVE_SCALE_NONE; } /** @@ -140,21 +142,16 @@ final class VibrationScaler { return effect; } - int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint); - int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); - - if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) { - // Bypassing user settings, or it has changed between checking and scaling. Use default. - currentIntensity = defaultIntensity; - } - - int newEffectStrength = intensityToEffectStrength(currentIntensity); - ScaleLevel scaleLevel = mScaleLevels.get(currentIntensity - defaultIntensity); + int newEffectStrength = getEffectStrength(usageHint); + ScaleLevel scaleLevel = mScaleLevels.get(getScaleLevel(usageHint)); + float adaptiveScale = getAdaptiveHapticsScale(usageHint); if (scaleLevel == null) { // Something about our scaling has gone wrong, so just play with no scaling. - Slog.e(TAG, "No configured scaling level!" - + " (current=" + currentIntensity + ", default= " + defaultIntensity + ")"); + Slog.e(TAG, "No configured scaling level found! (current=" + + mSettingsController.getCurrentIntensity(usageHint) + ", default= " + + mSettingsController.getDefaultIntensity(usageHint) + ")"); + scaleLevel = SCALE_LEVEL_NONE; } VibrationEffect.Composed composedEffect = (VibrationEffect.Composed) effect; @@ -162,20 +159,11 @@ final class VibrationScaler { new ArrayList<>(composedEffect.getSegments()); int segmentCount = segments.size(); for (int i = 0; i < segmentCount; i++) { - VibrationEffectSegment segment = segments.get(i); - segment = segment.resolve(mDefaultVibrationAmplitude) - .applyEffectStrength(newEffectStrength); - if (scaleLevel != null) { - segment = segment.scale(scaleLevel.factor); - } - - // If adaptive haptics scaling is available for this usage, apply it to the segment. - if (shouldApplyAdaptiveHapticsScale(usageHint)) { - float adaptiveScale = mAdaptiveHapticsScales.get(usageHint); - segment = segment.scaleLinearly(adaptiveScale); - } - - segments.set(i, segment); + segments.set(i, + segments.get(i).resolve(mDefaultVibrationAmplitude) + .applyEffectStrength(newEffectStrength) + .scale(scaleLevel.factor) + .scaleLinearly(adaptiveScale)); } if (segments.equals(composedEffect.getSegments())) { // No segment was updated, return original effect. @@ -197,15 +185,7 @@ final class VibrationScaler { * updated effect strength */ public PrebakedSegment scale(PrebakedSegment prebaked, int usageHint) { - int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); - - if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) { - // Bypassing user settings, or it has changed between checking and scaling. Use default. - currentIntensity = mSettingsController.getDefaultIntensity(usageHint); - } - - int newEffectStrength = intensityToEffectStrength(currentIntensity); - return prebaked.applyEffectStrength(newEffectStrength); + return prebaked.applyEffectStrength(getEffectStrength(usageHint)); } /** @@ -213,8 +193,6 @@ final class VibrationScaler { * * @param usageHint one of VibrationAttributes.USAGE_*. * @param scale The scaling factor that should be applied to vibrations of this usage. - * - * @hide */ public void updateAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint, float scale) { mAdaptiveHapticsScales.put(usageHint, scale); @@ -224,24 +202,68 @@ final class VibrationScaler { * Removes the usage from the cached adaptive haptics scales list. * * @param usageHint one of VibrationAttributes.USAGE_*. - * - * @hide */ public void removeAdaptiveHapticsScale(@VibrationAttributes.Usage int usageHint) { mAdaptiveHapticsScales.remove(usageHint); } - /** - * Removes all cached adaptive haptics scales. - * - * @hide - */ + /** Removes all cached adaptive haptics scales. */ public void clearAdaptiveHapticsScales() { mAdaptiveHapticsScales.clear(); } - private boolean shouldApplyAdaptiveHapticsScale(int usageHint) { - return Flags.adaptiveHapticsEnabled() && mAdaptiveHapticsScales.contains(usageHint); + /** Write current settings into given {@link PrintWriter}. */ + void dump(IndentingPrintWriter pw) { + pw.println("VibrationScaler:"); + pw.increaseIndent(); + pw.println("defaultVibrationAmplitude = " + mDefaultVibrationAmplitude); + + pw.println("ScaleLevels:"); + pw.increaseIndent(); + for (int i = 0; i < mScaleLevels.size(); i++) { + int scaleLevelKey = mScaleLevels.keyAt(i); + ScaleLevel scaleLevel = mScaleLevels.valueAt(i); + pw.println(scaleLevelToString(scaleLevelKey) + " = " + scaleLevel); + } + pw.decreaseIndent(); + + pw.println("AdaptiveHapticsScales:"); + pw.increaseIndent(); + for (int i = 0; i < mAdaptiveHapticsScales.size(); i++) { + int usage = mAdaptiveHapticsScales.keyAt(i); + float scale = mAdaptiveHapticsScales.valueAt(i); + pw.println(VibrationAttributes.usageToString(usage) + + " = " + String.format(Locale.ROOT, "%.2f", scale)); + } + pw.decreaseIndent(); + + pw.decreaseIndent(); + } + + /** Write current settings into given {@link ProtoOutputStream}. */ + void dump(ProtoOutputStream proto) { + proto.write(VibratorManagerServiceDumpProto.DEFAULT_VIBRATION_AMPLITUDE, + mDefaultVibrationAmplitude); + } + + @Override + public String toString() { + return "VibrationScaler{" + + "mScaleLevels=" + mScaleLevels + + ", mDefaultVibrationAmplitude=" + mDefaultVibrationAmplitude + + ", mAdaptiveHapticsScales=" + mAdaptiveHapticsScales + + '}'; + } + + private int getEffectStrength(int usageHint) { + int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); + + if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) { + // Bypassing user settings, or it has changed between checking and scaling. Use default. + currentIntensity = mSettingsController.getDefaultIntensity(usageHint); + } + + return intensityToEffectStrength(currentIntensity); } /** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */ @@ -259,6 +281,17 @@ final class VibrationScaler { } } + static String scaleLevelToString(int scaleLevel) { + return switch (scaleLevel) { + case SCALE_VERY_LOW -> "VERY_LOW"; + case SCALE_LOW -> "LOW"; + case SCALE_NONE -> "NONE"; + case SCALE_HIGH -> "HIGH"; + case SCALE_VERY_HIGH -> "VERY_HIGH"; + default -> String.valueOf(scaleLevel); + }; + } + /** Represents the scale that must be applied to a vibration effect intensity. */ private static final class ScaleLevel { public final float factor; diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index 99ce3e2fb740..5b77433fa6d9 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -386,7 +386,6 @@ final class VibrationSettings { * Returns the duration, in milliseconds, that the vibrator control service will wait for new * vibration params. * @return The request vibration params timeout in milliseconds. - * @hide */ public int getRequestVibrationParamsTimeoutMs() { return mVibrationConfig.getRequestVibrationParamsTimeoutMs(); @@ -645,11 +644,16 @@ final class VibrationSettings { .append("), "); } vibrationIntensitiesString.append('}'); + String keyboardVibrationOnString = mKeyboardVibrationOn + + " (default: " + mVibrationConfig.isDefaultKeyboardVibrationEnabled() + ")"; return "VibrationSettings{" + "mVibratorConfig=" + mVibrationConfig + + ", mVibrateOn=" + mVibrateOn + + ", mKeyboardVibrationOn=" + keyboardVibrationOnString + ", mVibrateInputDevices=" + mVibrateInputDevices + ", mBatterySaverMode=" + mBatterySaverMode - + ", mVibrateOn=" + mVibrateOn + + ", mRingerMode=" + ringerModeToString(mRingerMode) + + ", mOnWirelessCharger=" + mOnWirelessCharger + ", mVibrationIntensities=" + vibrationIntensitiesString + ", mProcStatesCache=" + mUidObserver.mProcStatesCache + '}'; @@ -658,32 +662,40 @@ final class VibrationSettings { /** Write current settings into given {@link PrintWriter}. */ void dump(IndentingPrintWriter pw) { - pw.println("VibrationSettings:"); - pw.increaseIndent(); - pw.println("vibrateOn = " + mVibrateOn); - pw.println("vibrateInputDevices = " + mVibrateInputDevices); - pw.println("batterySaverMode = " + mBatterySaverMode); - pw.println("VibrationIntensities:"); + synchronized (mLock) { + pw.println("VibrationSettings:"); + pw.increaseIndent(); + pw.println("vibrateOn = " + mVibrateOn); + pw.println("keyboardVibrationOn = " + mKeyboardVibrationOn + + ", default: " + mVibrationConfig.isDefaultKeyboardVibrationEnabled()); + pw.println("vibrateInputDevices = " + mVibrateInputDevices); + pw.println("batterySaverMode = " + mBatterySaverMode); + pw.println("ringerMode = " + ringerModeToString(mRingerMode)); + pw.println("onWirelessCharger = " + mOnWirelessCharger); + pw.println("processStateCache size = " + mUidObserver.mProcStatesCache.size()); + + pw.println("VibrationIntensities:"); + pw.increaseIndent(); + for (int i = 0; i < mCurrentVibrationIntensities.size(); i++) { + int usage = mCurrentVibrationIntensities.keyAt(i); + int intensity = mCurrentVibrationIntensities.valueAt(i); + pw.println(VibrationAttributes.usageToString(usage) + " = " + + intensityToString(intensity) + + ", default: " + intensityToString(getDefaultIntensity(usage))); + } + pw.decreaseIndent(); - pw.increaseIndent(); - for (int i = 0; i < mCurrentVibrationIntensities.size(); i++) { - int usage = mCurrentVibrationIntensities.keyAt(i); - int intensity = mCurrentVibrationIntensities.valueAt(i); - pw.println(VibrationAttributes.usageToString(usage) + " = " - + intensityToString(intensity) - + ", default: " + intensityToString(getDefaultIntensity(usage))); + mVibrationConfig.dumpWithoutDefaultSettings(pw); + pw.decreaseIndent(); } - pw.decreaseIndent(); - - mVibrationConfig.dumpWithoutDefaultSettings(pw); - pw.println("processStateCache = " + mUidObserver.mProcStatesCache); - pw.decreaseIndent(); } /** Write current settings into given {@link ProtoOutputStream}. */ void dump(ProtoOutputStream proto) { synchronized (mLock) { proto.write(VibratorManagerServiceDumpProto.VIBRATE_ON, mVibrateOn); + proto.write(VibratorManagerServiceDumpProto.KEYBOARD_VIBRATION_ON, + mKeyboardVibrationOn); proto.write(VibratorManagerServiceDumpProto.LOW_POWER_MODE, mBatterySaverMode); proto.write(VibratorManagerServiceDumpProto.ALARM_INTENSITY, getCurrentIntensity(USAGE_ALARM)); @@ -723,18 +735,22 @@ final class VibrationSettings { } private static String intensityToString(int intensity) { - switch (intensity) { - case Vibrator.VIBRATION_INTENSITY_OFF: - return "OFF"; - case Vibrator.VIBRATION_INTENSITY_LOW: - return "LOW"; - case Vibrator.VIBRATION_INTENSITY_MEDIUM: - return "MEDIUM"; - case Vibrator.VIBRATION_INTENSITY_HIGH: - return "HIGH"; - default: - return "UNKNOWN INTENSITY " + intensity; - } + return switch (intensity) { + case Vibrator.VIBRATION_INTENSITY_OFF -> "OFF"; + case Vibrator.VIBRATION_INTENSITY_LOW -> "LOW"; + case Vibrator.VIBRATION_INTENSITY_MEDIUM -> "MEDIUM"; + case Vibrator.VIBRATION_INTENSITY_HIGH -> "HIGH"; + default -> "UNKNOWN INTENSITY " + intensity; + }; + } + + private static String ringerModeToString(int ringerMode) { + return switch (ringerMode) { + case AudioManager.RINGER_MODE_SILENT -> "silent"; + case AudioManager.RINGER_MODE_VIBRATE -> "vibrate"; + case AudioManager.RINGER_MODE_NORMAL -> "normal"; + default -> String.valueOf(ringerMode); + }; } @VibrationIntensity diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java index f6af9ad991ff..f510b4e8ab30 100644 --- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java +++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java @@ -161,7 +161,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { waitForVibrationParamsIfRequired(); } // Scale resolves the default amplitudes from the effect before scaling them. - mVibration.scaleEffects(mVibrationScaler::scale); + mVibration.scaleEffects(mVibrationScaler); } else { mVibration.resolveEffects(mVibrationScaler.getDefaultVibrationAmplitude()); } diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java index 17a9e3330375..ec3d99b24656 100644 --- a/services/core/java/com/android/server/vibrator/VibratorControlService.java +++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java @@ -30,6 +30,7 @@ import static android.os.VibrationAttributes.USAGE_UNKNOWN; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; +import android.content.Context; import android.frameworks.vibrator.IVibratorControlService; import android.frameworks.vibrator.IVibratorController; import android.frameworks.vibrator.ScaleParam; @@ -37,27 +38,38 @@ import android.frameworks.vibrator.VibrationParam; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.SystemClock; import android.os.VibrationAttributes; +import android.os.VibrationEffect; +import android.util.IndentingPrintWriter; +import android.util.IntArray; import android.util.Slog; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; import java.util.Objects; import java.util.concurrent.CompletableFuture; /** * Implementation of {@link IVibratorControlService} which allows the registration of * {@link IVibratorController} to set and receive vibration params. - * - * @hide */ -public final class VibratorControlService extends IVibratorControlService.Stub { +final class VibratorControlService extends IVibratorControlService.Stub { private static final String TAG = "VibratorControlService"; private static final int UNRECOGNIZED_VIBRATION_TYPE = -1; private static final int NO_SCALE = -1; + private static final SimpleDateFormat DEBUG_DATE_TIME_FORMAT = + new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); + + private final VibrationParamsRecords mVibrationParamsRecords; private final VibratorControllerHolder mVibratorControllerHolder; private final VibrationScaler mVibrationScaler; private final Object mLock; @@ -68,25 +80,32 @@ public final class VibratorControlService extends IVibratorControlService.Stub { @GuardedBy("mLock") private IBinder mRequestVibrationParamsToken; - public VibratorControlService(VibratorControllerHolder vibratorControllerHolder, - VibrationScaler vibrationScaler, VibrationSettings vibrationSettings, Object lock) { + VibratorControlService(Context context, + VibratorControllerHolder vibratorControllerHolder, VibrationScaler vibrationScaler, + VibrationSettings vibrationSettings, Object lock) { mVibratorControllerHolder = vibratorControllerHolder; mVibrationScaler = vibrationScaler; mLock = lock; mRequestVibrationParamsForUsages = vibrationSettings.getRequestVibrationParamsForUsages(); + + int dumpSizeLimit = context.getResources().getInteger( + com.android.internal.R.integer.config_previousVibrationsDumpSizeLimit); + int dumpAggregationTimeLimit = context.getResources().getInteger( + com.android.internal.R.integer + .config_previousVibrationsDumpAggregationTimeMillisLimit); + mVibrationParamsRecords = + new VibrationParamsRecords(dumpSizeLimit, dumpAggregationTimeLimit); } @Override - public void registerVibratorController(IVibratorController controller) - throws RemoteException { + public void registerVibratorController(IVibratorController controller) { synchronized (mLock) { mVibratorControllerHolder.setVibratorController(controller); } } @Override - public void unregisterVibratorController(@NonNull IVibratorController controller) - throws RemoteException { + public void unregisterVibratorController(@NonNull IVibratorController controller) { Objects.requireNonNull(controller); synchronized (mLock) { @@ -110,7 +129,7 @@ public final class VibratorControlService extends IVibratorControlService.Stub { @Override public void setVibrationParams(@SuppressLint("ArrayReturn") VibrationParam[] params, - @NonNull IVibratorController token) throws RemoteException { + @NonNull IVibratorController token) { Objects.requireNonNull(token); synchronized (mLock) { @@ -128,12 +147,12 @@ public final class VibratorControlService extends IVibratorControlService.Stub { } updateAdaptiveHapticsScales(params); + recordUpdateVibrationParams(params, /* fromRequest= */ false); } } @Override - public void clearVibrationParams(int types, @NonNull IVibratorController token) - throws RemoteException { + public void clearVibrationParams(int types, @NonNull IVibratorController token) { Objects.requireNonNull(token); synchronized (mLock) { @@ -151,13 +170,13 @@ public final class VibratorControlService extends IVibratorControlService.Stub { } updateAdaptiveHapticsScales(types, NO_SCALE); + recordClearVibrationParams(types); } } @Override public void onRequestVibrationParamsComplete( - @NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result) - throws RemoteException { + @NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result) { Objects.requireNonNull(requestToken); synchronized (mLock) { @@ -177,16 +196,17 @@ public final class VibratorControlService extends IVibratorControlService.Stub { updateAdaptiveHapticsScales(result); endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false); + recordUpdateVibrationParams(result, /* fromRequest= */ true); } } @Override - public int getInterfaceVersion() throws RemoteException { + public int getInterfaceVersion() { return this.VERSION; } @Override - public String getInterfaceHash() throws RemoteException { + public String getInterfaceHash() { return this.HASH; } @@ -266,6 +286,42 @@ public final class VibratorControlService extends IVibratorControlService.Stub { } } + /** Write current settings into given {@link PrintWriter}. */ + void dump(IndentingPrintWriter pw) { + boolean isVibratorControllerRegistered; + boolean hasPendingVibrationParamsRequest; + synchronized (mLock) { + isVibratorControllerRegistered = + mVibratorControllerHolder.getVibratorController() != null; + hasPendingVibrationParamsRequest = mRequestVibrationParamsFuture != null; + } + + pw.println("VibratorControlService:"); + pw.increaseIndent(); + pw.println("isVibratorControllerRegistered = " + isVibratorControllerRegistered); + pw.println("hasPendingVibrationParamsRequest = " + hasPendingVibrationParamsRequest); + + pw.println(); + pw.println("Vibration parameters update history:"); + pw.increaseIndent(); + mVibrationParamsRecords.dump(pw); + pw.decreaseIndent(); + + pw.decreaseIndent(); + } + + /** Write current settings into given {@link ProtoOutputStream}. */ + void dump(ProtoOutputStream proto) { + boolean isVibratorControllerRegistered; + synchronized (mLock) { + isVibratorControllerRegistered = + mVibratorControllerHolder.getVibratorController() != null; + } + proto.write(VibratorManagerServiceDumpProto.IS_VIBRATOR_CONTROLLER_REGISTERED, + isVibratorControllerRegistered); + mVibrationParamsRecords.dump(proto); + } + /** * Completes or cancels the vibration params request future and resets the future and token * to null. @@ -312,6 +368,33 @@ public final class VibratorControlService extends IVibratorControlService.Stub { } } + private static int[] mapFromAdaptiveVibrationTypeToVibrationUsages(int types) { + IntArray usages = new IntArray(15); + if ((ScaleParam.TYPE_ALARM & types) != 0) { + usages.add(USAGE_ALARM); + } + + if ((ScaleParam.TYPE_NOTIFICATION & types) != 0) { + usages.add(USAGE_NOTIFICATION); + usages.add(USAGE_COMMUNICATION_REQUEST); + } + + if ((ScaleParam.TYPE_RINGTONE & types) != 0) { + usages.add(USAGE_RINGTONE); + } + + if ((ScaleParam.TYPE_MEDIA & types) != 0) { + usages.add(USAGE_MEDIA); + usages.add(USAGE_UNKNOWN); + } + + if ((ScaleParam.TYPE_INTERACTIVE & types) != 0) { + usages.add(USAGE_TOUCH); + usages.add(USAGE_HARDWARE_FEEDBACK); + } + return usages.toArray(); + } + /** * Updates the adaptive haptics scales cached in {@link VibrationScaler} with the * provided params. @@ -319,7 +402,14 @@ public final class VibratorControlService extends IVibratorControlService.Stub { * @param params the new vibration params. */ private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) { + if (params == null) { + return; + } for (VibrationParam param : params) { + if (param.getTag() != VibrationParam.scale) { + Slog.e(TAG, "Unsupported vibration param: " + param); + continue; + } ScaleParam scaleParam = param.getScale(); updateAdaptiveHapticsScales(scaleParam.typesMask, scaleParam.scale); } @@ -333,27 +423,8 @@ public final class VibratorControlService extends IVibratorControlService.Stub { * @param scale The scaling factor that should be applied to the vibrations. */ private void updateAdaptiveHapticsScales(int types, float scale) { - if ((ScaleParam.TYPE_ALARM & types) != 0) { - updateOrRemoveAdaptiveHapticsScale(USAGE_ALARM, scale); - } - - if ((ScaleParam.TYPE_NOTIFICATION & types) != 0) { - updateOrRemoveAdaptiveHapticsScale(USAGE_NOTIFICATION, scale); - updateOrRemoveAdaptiveHapticsScale(USAGE_COMMUNICATION_REQUEST, scale); - } - - if ((ScaleParam.TYPE_RINGTONE & types) != 0) { - updateOrRemoveAdaptiveHapticsScale(USAGE_RINGTONE, scale); - } - - if ((ScaleParam.TYPE_MEDIA & types) != 0) { - updateOrRemoveAdaptiveHapticsScale(USAGE_MEDIA, scale); - updateOrRemoveAdaptiveHapticsScale(USAGE_UNKNOWN, scale); - } - - if ((ScaleParam.TYPE_INTERACTIVE & types) != 0) { - updateOrRemoveAdaptiveHapticsScale(USAGE_TOUCH, scale); - updateOrRemoveAdaptiveHapticsScale(USAGE_HARDWARE_FEEDBACK, scale); + for (int usage : mapFromAdaptiveVibrationTypeToVibrationUsages(types)) { + updateOrRemoveAdaptiveHapticsScale(usage, scale); } } @@ -375,4 +446,136 @@ public final class VibratorControlService extends IVibratorControlService.Stub { mVibrationScaler.updateAdaptiveHapticsScale(usageHint, scale); } + + private void recordUpdateVibrationParams(@Nullable VibrationParam[] params, + boolean fromRequest) { + if (params == null) { + return; + } + VibrationParamsRecords.Operation operation = + fromRequest ? VibrationParamsRecords.Operation.PULL + : VibrationParamsRecords.Operation.PUSH; + long createTime = SystemClock.uptimeMillis(); + for (VibrationParam param : params) { + if (param.getTag() != VibrationParam.scale) { + Slog.w(TAG, "Unsupported vibration param ignored from dumpsys records: " + param); + continue; + } + ScaleParam scaleParam = param.getScale(); + mVibrationParamsRecords.add(new VibrationScaleParamRecord(operation, createTime, + scaleParam.typesMask, scaleParam.scale)); + } + } + + private void recordClearVibrationParams(int typesMask) { + long createTime = SystemClock.uptimeMillis(); + mVibrationParamsRecords.add(new VibrationScaleParamRecord( + VibrationParamsRecords.Operation.CLEAR, createTime, typesMask, NO_SCALE)); + } + + /** + * Keep records of {@link VibrationParam} values received by this service from a registered + * {@link VibratorController} and provide debug information for this service. + */ + private static final class VibrationParamsRecords + extends GroupedAggregatedLogRecords<VibrationScaleParamRecord> { + + /** The type of operations on vibration parameters that the service is recording. */ + enum Operation { + PULL, PUSH, CLEAR + }; + + VibrationParamsRecords(int sizeLimit, int aggregationTimeLimit) { + super(sizeLimit, aggregationTimeLimit); + } + + @Override + synchronized void dumpGroupHeader(IndentingPrintWriter pw, int paramType) { + if (paramType == VibrationParam.scale) { + pw.println("SCALE:"); + } else { + pw.println("UNKNOWN:"); + } + } + + @Override + synchronized long findGroupKeyProtoFieldId(int usage) { + return VibratorManagerServiceDumpProto.PREVIOUS_VIBRATION_PARAMS; + } + } + + /** + * Record for a single {@link Vibration.DebugInfo}, that can be grouped by usage and aggregated + * by UID, {@link VibrationAttributes} and {@link VibrationEffect}. + */ + private static final class VibrationScaleParamRecord + implements GroupedAggregatedLogRecords.SingleLogRecord { + + private final VibrationParamsRecords.Operation mOperation; + private final long mCreateTime; + private final int mTypesMask; + private final float mScale; + + VibrationScaleParamRecord(VibrationParamsRecords.Operation operation, long createTime, + int typesMask, float scale) { + mOperation = operation; + mCreateTime = createTime; + mTypesMask = typesMask; + mScale = scale; + } + + @Override + public int getGroupKey() { + return VibrationParam.scale; + } + + @Override + public long getCreateUptimeMs() { + return mCreateTime; + } + + @Override + public boolean mayAggregate(GroupedAggregatedLogRecords.SingleLogRecord record) { + if (!(record instanceof VibrationScaleParamRecord param)) { + return false; + } + return mTypesMask == param.mTypesMask && mOperation == param.mOperation; + } + + @Override + public void dump(IndentingPrintWriter pw) { + String line = String.format(Locale.ROOT, + "%s | %6s | scale: %5s | typesMask: %6s | usages: %s", + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)), + mOperation.name().toLowerCase(Locale.ROOT), + (mScale == NO_SCALE) ? "" : String.format(Locale.ROOT, "%.2f", mScale), + Long.toBinaryString(mTypesMask), createVibrationUsagesString()); + pw.println(line); + } + + @Override + public void dump(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(VibrationParamProto.CREATE_TIME, mCreateTime); + proto.write(VibrationParamProto.IS_FROM_REQUEST, + mOperation == VibrationParamsRecords.Operation.PULL); + + final long scaleToken = proto.start(VibrationParamProto.SCALE); + proto.write(VibrationScaleParamProto.TYPES_MASK, mTypesMask); + proto.write(VibrationScaleParamProto.SCALE, mScale); + proto.end(scaleToken); + + proto.end(token); + } + + private String createVibrationUsagesString() { + StringBuilder sb = new StringBuilder(); + int[] usages = mapFromAdaptiveVibrationTypeToVibrationUsages(mTypesMask); + for (int i = 0; i < usages.length; i++) { + if (i > 0) sb.append(", "); + sb.append(VibrationAttributes.usageToString(usages[i])); + } + return sb.toString(); + } + } } diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index f5d4d1e3926b..6710d02bee90 100644 --- a/services/core/java/com/android/server/vibrator/VibratorController.java +++ b/services/core/java/com/android/server/vibrator/VibratorController.java @@ -17,7 +17,6 @@ package com.android.server.vibrator; import android.annotation.Nullable; -import android.annotation.Nullable; import android.hardware.vibrator.IVibrator; import android.os.Binder; import android.os.IVibratorStateListener; @@ -354,13 +353,13 @@ final class VibratorController { } void dump(IndentingPrintWriter pw) { - pw.println("VibratorController:"); + pw.println("Vibrator (id=" + mVibratorInfo.getId() + "):"); pw.increaseIndent(); pw.println("isVibrating = " + mIsVibrating); pw.println("isUnderExternalControl = " + mIsUnderExternalControl); pw.println("currentAmplitude = " + mCurrentAmplitude); pw.println("vibratorInfoLoadSuccessful = " + mVibratorInfoLoadSuccessful); - pw.println("vibratorStateListenerCount = " + pw.println("vibratorStateListener size = " + mVibratorStateListeners.getRegisteredCallbackCount()); mVibratorInfo.dump(pw); pw.decreaseIndent(); diff --git a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java index 79a99b3ee2ff..b49fb85ecf3f 100644 --- a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java +++ b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java @@ -24,8 +24,6 @@ import android.util.Slog; /** * Holder class for {@link IVibratorController}. - * - * @hide */ public final class VibratorControllerHolder implements IBinder.DeathRecipient { private static final String TAG = "VibratorControllerHolder"; diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 78e0ebbb53fa..c1bf0393fc85 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -16,7 +16,6 @@ package com.android.server.vibrator; -import static android.os.ExternalVibrationScale.ScaleLevel.SCALE_MUTE; import static android.os.VibrationEffect.VibrationParameter.targetAmplitude; import static android.os.VibrationEffect.VibrationParameter.targetFrequency; @@ -84,7 +83,6 @@ import java.lang.ref.WeakReference; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; -import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -217,7 +215,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mVibrationSettings = new VibrationSettings(mContext, mHandler); mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings); - mVibratorControlService = new VibratorControlService( + mVibratorControlService = new VibratorControlService(mContext, injector.createVibratorControllerHolder(), mVibrationScaler, mVibrationSettings, mLock); mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler); @@ -416,14 +414,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @Override // Binder call - public void performHapticFeedback( - int uid, int deviceId, String opPkg, int constant, boolean always, String reason) { + public void performHapticFeedback(int uid, int deviceId, String opPkg, int constant, + boolean always, String reason, boolean fromIme) { // Note that the `performHapticFeedback` method does not take a token argument from the // caller, and instead, uses this service as the token. This is to mitigate performance // impact that would otherwise be caused due to marshal latency. Haptic feedback effects are // short-lived, so we don't need to cancel when the process dies. performHapticFeedbackInternal( - uid, deviceId, opPkg, constant, always, reason, /* token= */ this); + uid, deviceId, opPkg, constant, always, reason, /* token= */ this, fromIme); } /** @@ -435,7 +433,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @Nullable HalVibration performHapticFeedbackInternal( int uid, int deviceId, String opPkg, int constant, boolean always, String reason, - IBinder token) { + IBinder token, boolean fromIme) { HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider(); if (hapticVibrationProvider == null) { Slog.w(TAG, "performHapticFeedback; haptic vibration provider not ready."); @@ -449,7 +447,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { CombinedVibration combinedVibration = CombinedVibration.createParallel(effect); VibrationAttributes attrs = hapticVibrationProvider.getVibrationAttributesForHapticFeedback( - constant, /* bypassVibrationIntensitySetting= */ always); + constant, /* bypassVibrationIntensitySetting= */ always, fromIme); VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, constant); return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, combinedVibration, attrs, "performHapticFeedback: " + reason, token); @@ -639,13 +637,16 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } IndentingPrintWriter pw = new IndentingPrintWriter(w, /* singleIndent= */ " "); synchronized (mLock) { - pw.println("Vibrator Manager Service:"); + pw.println("VibratorManagerService:"); pw.increaseIndent(); mVibrationSettings.dump(pw); pw.println(); - pw.println("VibratorControllers:"); + mVibrationScaler.dump(pw); + pw.println(); + + pw.println("Vibrators:"); pw.increaseIndent(); for (int i = 0; i < mVibrators.size(); i++) { mVibrators.valueAt(i).dump(pw); @@ -686,6 +687,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { pw.println(); pw.println(); mVibratorManagerRecords.dump(pw); + + pw.println(); + pw.println(); + mVibratorControlService.dump(pw); } private void dumpProto(FileDescriptor fd) { @@ -695,6 +700,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } synchronized (mLock) { mVibrationSettings.dump(proto); + mVibrationScaler.dump(proto); if (mCurrentVibration != null) { mCurrentVibration.getVibration().getDebugInfo().dump(proto, VibratorManagerServiceDumpProto.CURRENT_VIBRATION); @@ -716,6 +722,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { isUnderExternalControl); } mVibratorManagerRecords.dump(proto); + mVibratorControlService.dump(proto); proto.flush(); } @@ -887,7 +894,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (!vib.callerInfo.attrs.isFlagSet( VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) { // Scale resolves the default amplitudes from the effect before scaling them. - vib.scaleEffects(mVibrationScaler::scale); + vib.scaleEffects(mVibrationScaler); } else { vib.resolveEffects(mVibrationScaler.getDefaultVibrationAmplitude()); } @@ -1663,7 +1670,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { public Vibration.DebugInfo getDebugInfo() { return new Vibration.DebugInfo(mStatus, stats, /* playedEffect= */ null, - /* originalEffect= */ null, scale.scaleLevel, callerInfo); + /* originalEffect= */ null, scale.scaleLevel, scale.adaptiveHapticsScale, + callerInfo); } public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) { @@ -1739,8 +1747,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { int aggregationTimeLimit) { mAggregatedVibrationHistory = new VibrationRecords(aggregationSizeLimit, aggregationTimeLimit); - mRecentVibrations = new VibrationRecords( - recentVibrationSizeLimit, /* aggregationTimeLimit= */ 0); + // Recent vibrations are not aggregated, to help debugging issues that just happened. + mRecentVibrations = + new VibrationRecords(recentVibrationSizeLimit, /* aggregationTimeLimit= */ 0); } synchronized void record(HalVibration vib) { @@ -1752,9 +1761,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } private synchronized void record(Vibration.DebugInfo info) { - AggregatedVibrationRecord removedRecord = mRecentVibrations.record(info); - if (removedRecord != null) { - mAggregatedVibrationHistory.record(removedRecord.mLatestVibration); + GroupedAggregatedLogRecords.AggregatedLogRecord<VibrationRecord> droppedRecord = + mRecentVibrations.add(new VibrationRecord(info)); + if (droppedRecord != null) { + // Move dropped record from recent list to aggregated history list. + mAggregatedVibrationHistory.add(droppedRecord.getLatest()); } } @@ -1763,9 +1774,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { pw.increaseIndent(); mRecentVibrations.dump(pw); pw.decreaseIndent(); + pw.println(); pw.println(); - pw.println("Aggregated vibration history:"); pw.increaseIndent(); mAggregatedVibrationHistory.dump(pw); @@ -1778,127 +1789,75 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } /** Keep records of vibrations played and provide debug information for this service. */ - private static final class VibrationRecords { - private final SparseArray<LinkedList<AggregatedVibrationRecord>> mVibrations = - new SparseArray<>(); - private final int mSizeLimit; - private final int mAggregationTimeLimit; + private static final class VibrationRecords + extends GroupedAggregatedLogRecords<VibrationRecord> { VibrationRecords(int sizeLimit, int aggregationTimeLimit) { - mSizeLimit = sizeLimit; - mAggregationTimeLimit = aggregationTimeLimit; + super(sizeLimit, aggregationTimeLimit); } - synchronized AggregatedVibrationRecord record(Vibration.DebugInfo info) { - int usage = info.mCallerInfo.attrs.getUsage(); - if (!mVibrations.contains(usage)) { - mVibrations.put(usage, new LinkedList<>()); - } - LinkedList<AggregatedVibrationRecord> records = mVibrations.get(usage); - if (mAggregationTimeLimit > 0 && !records.isEmpty()) { - AggregatedVibrationRecord lastRecord = records.getLast(); - if (lastRecord.mayAggregate(info, mAggregationTimeLimit)) { - lastRecord.record(info); - return null; - } - } - AggregatedVibrationRecord removedRecord = null; - if (records.size() > mSizeLimit) { - removedRecord = records.removeFirst(); - } - records.addLast(new AggregatedVibrationRecord(info)); - return removedRecord; - } - - synchronized void dump(IndentingPrintWriter pw) { - for (int i = 0; i < mVibrations.size(); i++) { - pw.println(VibrationAttributes.usageToString(mVibrations.keyAt(i)) + ":"); - pw.increaseIndent(); - for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) { - info.dump(pw); - } - pw.decreaseIndent(); - pw.println(); - } - } - - synchronized void dump(ProtoOutputStream proto) { - for (int i = 0; i < mVibrations.size(); i++) { - long fieldId; - switch (mVibrations.keyAt(i)) { - case VibrationAttributes.USAGE_RINGTONE: - fieldId = VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS; - break; - case VibrationAttributes.USAGE_NOTIFICATION: - fieldId = VibratorManagerServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS; - break; - case VibrationAttributes.USAGE_ALARM: - fieldId = VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS; - break; - default: - fieldId = VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS; - } - for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) { - if (info.mLatestVibration.mPlayedEffect == null) { - // External vibrations are reported separately in the dump proto - info.dump(proto, - VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS); - } else { - info.dump(proto, fieldId); - } - } - } + @Override + void dumpGroupHeader(IndentingPrintWriter pw, int usage) { + pw.println(VibrationAttributes.usageToString(usage) + ":"); } - synchronized void dumpOnSingleField(ProtoOutputStream proto, long fieldId) { - for (int i = 0; i < mVibrations.size(); i++) { - for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) { - info.dump(proto, fieldId); - } - } + @Override + long findGroupKeyProtoFieldId(int usage) { + return switch (usage) { + case VibrationAttributes.USAGE_RINGTONE -> + VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS; + case VibrationAttributes.USAGE_NOTIFICATION -> + VibratorManagerServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS; + case VibrationAttributes.USAGE_ALARM -> + VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS; + default -> + VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS; + }; } } /** - * Record that keeps the last {@link Vibration.DebugInfo} played, aggregating close vibrations - * from the same uid that have the same {@link VibrationAttributes} and {@link VibrationEffect}. + * Record for a single {@link Vibration.DebugInfo}, that can be grouped by usage and aggregated + * by UID, {@link VibrationAttributes} and {@link VibrationEffect}. */ - private static final class AggregatedVibrationRecord { - private final Vibration.DebugInfo mFirstVibration; - private Vibration.DebugInfo mLatestVibration; - private int mVibrationCount; + private static final class VibrationRecord + implements GroupedAggregatedLogRecords.SingleLogRecord { + private final Vibration.DebugInfo mInfo; - AggregatedVibrationRecord(Vibration.DebugInfo info) { - mLatestVibration = mFirstVibration = info; - mVibrationCount = 1; + VibrationRecord(Vibration.DebugInfo info) { + mInfo = info; } - synchronized boolean mayAggregate(Vibration.DebugInfo info, long timeLimit) { - return Objects.equals(mLatestVibration.mCallerInfo.uid, info.mCallerInfo.uid) - && Objects.equals(mLatestVibration.mCallerInfo.attrs, info.mCallerInfo.attrs) - && Objects.equals(mLatestVibration.mPlayedEffect, info.mPlayedEffect) - && Math.abs(mLatestVibration.mCreateTime - info.mCreateTime) < timeLimit; + @Override + public int getGroupKey() { + return mInfo.mCallerInfo.attrs.getUsage(); } - synchronized void record(Vibration.DebugInfo vib) { - mLatestVibration = vib; - mVibrationCount++; + @Override + public long getCreateUptimeMs() { + return mInfo.mCreateTime; } - synchronized void dump(IndentingPrintWriter pw) { - mFirstVibration.dumpCompact(pw); - if (mVibrationCount == 1) { - return; - } - if (mVibrationCount > 2) { - pw.println( - "-> Skipping " + (mVibrationCount - 2) + " aggregated vibrations, latest:"); + @Override + public boolean mayAggregate(GroupedAggregatedLogRecords.SingleLogRecord record) { + if (!(record instanceof VibrationRecord)) { + return false; } - mLatestVibration.dumpCompact(pw); + Vibration.DebugInfo info = ((VibrationRecord) record).mInfo; + return mInfo.mCallerInfo.uid == info.mCallerInfo.uid + && Objects.equals(mInfo.mCallerInfo.attrs, info.mCallerInfo.attrs) + && Objects.equals(mInfo.mPlayedEffect, info.mPlayedEffect); } - synchronized void dump(ProtoOutputStream proto, long fieldId) { - mLatestVibration.dump(proto, fieldId); + @Override + public void dump(IndentingPrintWriter pw) { + // Prints a compact version of each vibration request for dumpsys. + mInfo.dumpCompact(pw); + } + + @Override + public void dump(ProtoOutputStream proto, long fieldId) { + mInfo.dump(proto, fieldId); } } @@ -2001,7 +1960,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @Override public ExternalVibrationScale onExternalVibrationStart(ExternalVibration vib) { - if (!hasExternalControlCapability()) { return SCALE_MUTE; } @@ -2085,10 +2043,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } mCurrentExternalVibration = vibHolder; vibHolder.linkToDeath(); - vibHolder.scale.scaleLevel = mVibrationScaler.getExternalVibrationScaleLevel( - attrs.getUsage()); - vibHolder.scale.adaptiveHapticsScale = mVibrationScaler.getAdaptiveHapticsScale( - attrs.getUsage()); + vibHolder.scale.scaleLevel = mVibrationScaler.getScaleLevel(attrs.getUsage()); + vibHolder.scale.adaptiveHapticsScale = + mVibrationScaler.getAdaptiveHapticsScale(attrs.getUsage()); } if (waitForCompletion) { @@ -2300,7 +2257,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { HalVibration vib = performHapticFeedbackInternal(Binder.getCallingUid(), Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, constant, /* always= */ commonOptions.force, /* reason= */ commonOptions.description, - deathBinder); + deathBinder, false /* fromIme */); maybeWaitOnVibration(vib, commonOptions); return 0; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 90cff3950047..92fde18233a9 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1476,7 +1476,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - private void scheduleConfigurationChanged(Configuration config) { + private void scheduleConfigurationChanged(@NonNull Configuration config, + @NonNull ActivityWindowInfo activityWindowInfo) { if (!attachedToProcess()) { ProtoLog.w(WM_DEBUG_CONFIGURATION, "Can't report activity configuration " + "update - client not running, activityRecord=%s", this); @@ -1487,7 +1488,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A + "config: %s", this, config); mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), - ActivityConfigurationChangeItem.obtain(token, config)); + ActivityConfigurationChangeItem.obtain(token, config, activityWindowInfo)); } catch (RemoteException e) { // If process died, whatever. } @@ -9785,7 +9786,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // configurations because there are cases (like moving a task to the root pinned task) where // the combine configurations are equal, but would otherwise differ in the override config mTmpConfig.setTo(mLastReportedConfiguration.getMergedConfiguration()); - if (getConfiguration().equals(mTmpConfig) && !displayChanged) { + final ActivityWindowInfo newActivityWindowInfo = getActivityWindowInfo(); + final boolean isActivityWindowInfoChanged = Flags.activityWindowInfoFlag() + && !mLastReportedActivityWindowInfo.equals(newActivityWindowInfo); + if (!displayChanged && !isActivityWindowInfoChanged + && getConfiguration().equals(mTmpConfig)) { ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration & display " + "unchanged in %s", this); return true; @@ -9800,7 +9805,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Update last reported values. final Configuration newMergedOverrideConfig = getMergedOverrideConfiguration(); - final ActivityWindowInfo newActivityWindowInfo = getActivityWindowInfo(); setLastReportedConfiguration(getProcessGlobalConfiguration(), newMergedOverrideConfig); setLastReportedActivityWindowInfo(newActivityWindowInfo); @@ -9823,7 +9827,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig, newActivityWindowInfo); } else { - scheduleConfigurationChanged(newMergedOverrideConfig); + scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo); } notifyDisplayCompatPolicyAboutConfigurationChange( mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig); @@ -9891,7 +9895,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig, newActivityWindowInfo); } else { - scheduleConfigurationChanged(newMergedOverrideConfig); + scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo); } notifyDisplayCompatPolicyAboutConfigurationChange( mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig); diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index cd968067e289..fa76774a604f 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -520,11 +520,37 @@ class InsetsSourceProvider { updateVisibility(); mControl = new InsetsSourceControl(mSource.getId(), mSource.getType(), leash, mClientVisible, surfacePosition, getInsetsHint()); + mStateController.notifySurfaceTransactionReady(this, getSurfaceTransactionId(leash), true); ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource Control %s for target %s", mControl, mControlTarget); } + private long getSurfaceTransactionId(SurfaceControl leash) { + // Here returns mNativeObject (long) as the ID instead of the leash itself so that + // InsetsStateController won't keep referencing the leash unexpectedly. + return leash != null ? leash.mNativeObject : 0; + } + + /** + * This is called when the surface transaction of the leash initialization has been committed. + * + * @param id Indicates which transaction is committed so that stale callbacks can be dropped. + */ + void onSurfaceTransactionCommitted(long id) { + if (mIsLeashReadyForDispatching) { + return; + } + if (mControl == null) { + return; + } + if (id != getSurfaceTransactionId(mControl.getLeash())) { + return; + } + mIsLeashReadyForDispatching = true; + mStateController.notifySurfaceTransactionReady(this, 0, false); + } + void startSeamlessRotation() { if (!mSeamlessRotating) { mSeamlessRotating = true; @@ -545,10 +571,6 @@ class InsetsSourceProvider { return true; } - void onSurfaceTransactionApplied() { - mIsLeashReadyForDispatching = true; - } - void setClientVisible(boolean clientVisible) { if (mClientVisible == clientVisible) { return; @@ -733,6 +755,7 @@ class InsetsSourceProvider { public void onAnimationCancelled(SurfaceControl animationLeash) { if (mAdapter == this) { mStateController.notifyControlRevoked(mControlTarget, InsetsSourceProvider.this); + mStateController.notifySurfaceTransactionReady(InsetsSourceProvider.this, 0, false); mControl = null; mControlTarget = null; mAdapter = null; diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 6b9fcf411ce1..ba578f642429 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -34,6 +34,7 @@ import android.os.Trace; import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; +import android.util.SparseLongArray; import android.util.proto.ProtoOutputStream; import android.view.InsetsSource; import android.view.InsetsSourceControl; @@ -58,6 +59,7 @@ class InsetsStateController { private final DisplayContent mDisplayContent; private final SparseArray<InsetsSourceProvider> mProviders = new SparseArray<>(); + private final SparseLongArray mSurfaceTransactionIds = new SparseLongArray(); private final ArrayMap<InsetsControlTarget, ArrayList<InsetsSourceProvider>> mControlTargetProvidersMap = new ArrayMap<>(); private final SparseArray<InsetsControlTarget> mIdControlTargetMap = new SparseArray<>(); @@ -360,14 +362,32 @@ class InsetsStateController { notifyPendingInsetsControlChanged(); } + void notifySurfaceTransactionReady(InsetsSourceProvider provider, long id, boolean ready) { + if (ready) { + mSurfaceTransactionIds.put(provider.getSource().getId(), id); + } else { + mSurfaceTransactionIds.delete(provider.getSource().getId()); + } + } + private void notifyPendingInsetsControlChanged() { if (mPendingControlChanged.isEmpty()) { return; } + final int size = mSurfaceTransactionIds.size(); + final SparseLongArray surfaceTransactionIds = new SparseLongArray(size); + for (int i = 0; i < size; i++) { + surfaceTransactionIds.append( + mSurfaceTransactionIds.keyAt(i), mSurfaceTransactionIds.valueAt(i)); + } mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { - for (int i = mProviders.size() - 1; i >= 0; i--) { - final InsetsSourceProvider provider = mProviders.valueAt(i); - provider.onSurfaceTransactionApplied(); + for (int i = 0; i < size; i++) { + final int sourceId = surfaceTransactionIds.keyAt(i); + final InsetsSourceProvider provider = mProviders.get(sourceId); + if (provider == null) { + continue; + } + provider.onSurfaceTransactionCommitted(surfaceTransactionIds.valueAt(i)); } final ArraySet<InsetsControlTarget> newControlTargets = new ArraySet<>(); int displayId = mDisplayContent.getDisplayId(); diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 908cbd340236..30134d815fa6 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -336,19 +336,19 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } @Override - public boolean performHapticFeedback(int effectId, boolean always) { + public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) { final long ident = Binder.clearCallingIdentity(); try { return mService.mPolicy.performHapticFeedback(mUid, mPackageName, - effectId, always, null); + effectId, always, null, fromIme); } finally { Binder.restoreCallingIdentity(ident); } } @Override - public void performHapticFeedbackAsync(int effectId, boolean always) { - performHapticFeedback(effectId, always); + public void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme) { + performHapticFeedback(effectId, always, fromIme); } /* Drag/drop */ diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index b43a4540bbde..5e7f1cbdd06e 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -28,6 +28,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.content.Context; +import android.os.HandlerExecutor; import android.os.Trace; import android.util.Slog; import android.util.TimeUtils; @@ -69,6 +70,8 @@ public class WindowAnimator { private Choreographer mChoreographer; + private final HandlerExecutor mExecutor; + /** * Indicates whether we have an animation frame callback scheduled, which will happen at * vsync-app and then schedule the animation tick at the right time (vsync-sf). @@ -80,8 +83,7 @@ public class WindowAnimator { * A list of runnable that need to be run after {@link WindowContainer#prepareSurfaces} is * executed and the corresponding transaction is closed and applied. */ - private final ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>(); - private boolean mInExecuteAfterPrepareSurfacesRunnables; + private ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>(); private final SurfaceControl.Transaction mTransaction; @@ -92,6 +94,7 @@ public class WindowAnimator { mTransaction = service.mTransactionFactory.get(); service.mAnimationHandler.runWithScissors( () -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */); + mExecutor = new HandlerExecutor(service.mAnimationHandler); mAnimationFrameCallback = frameTimeNs -> { synchronized (mService.mGlobalLock) { @@ -197,6 +200,19 @@ public class WindowAnimator { updateRunningExpensiveAnimationsLegacy(); } + final ArrayList<Runnable> afterPrepareSurfacesRunnables = mAfterPrepareSurfacesRunnables; + if (!afterPrepareSurfacesRunnables.isEmpty()) { + mAfterPrepareSurfacesRunnables = new ArrayList<>(); + mTransaction.addTransactionCommittedListener(mExecutor, () -> { + synchronized (mService.mGlobalLock) { + // Traverse in order they were added. + for (int i = 0, size = afterPrepareSurfacesRunnables.size(); i < size; i++) { + afterPrepareSurfacesRunnables.get(i).run(); + } + afterPrepareSurfacesRunnables.clear(); + } + }); + } Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "applyTransaction"); mTransaction.apply(); Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); @@ -204,7 +220,6 @@ public class WindowAnimator { ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate"); mService.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); - executeAfterPrepareSurfacesRunnables(); if (DEBUG_WINDOW_TRACE) { Slog.i(TAG, "!!! animate: exit" @@ -286,34 +301,10 @@ public class WindowAnimator { /** * Adds a runnable to be executed after {@link WindowContainer#prepareSurfaces} is called and - * the corresponding transaction is closed and applied. + * the corresponding transaction is closed, applied, and committed. */ void addAfterPrepareSurfacesRunnable(Runnable r) { - // If runnables are already being handled in executeAfterPrepareSurfacesRunnable, then just - // immediately execute the runnable passed in. - if (mInExecuteAfterPrepareSurfacesRunnables) { - r.run(); - return; - } - mAfterPrepareSurfacesRunnables.add(r); scheduleAnimation(); } - - void executeAfterPrepareSurfacesRunnables() { - - // Don't even think about to start recursing! - if (mInExecuteAfterPrepareSurfacesRunnables) { - return; - } - mInExecuteAfterPrepareSurfacesRunnables = true; - - // Traverse in order they were added. - final int size = mAfterPrepareSurfacesRunnables.size(); - for (int i = 0; i < size; i++) { - mAfterPrepareSurfacesRunnables.get(i).run(); - } - mAfterPrepareSurfacesRunnables.clear(); - mInExecuteAfterPrepareSurfacesRunnables = false; - } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c93cc074aa3d..7e06129832a8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -6706,7 +6706,7 @@ public class WindowManagerService extends IWindowManager.Stub private void dumpLogStatus(PrintWriter pw) { pw.println("WINDOW MANAGER LOGGING (dumpsys window logging)"); - if (android.tracing.Flags.perfettoProtolog()) { + if (android.tracing.Flags.perfettoProtologTracing()) { pw.println("Deprecated legacy command. Use Perfetto commands instead."); return; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index a7a28c282ff9..18ac0e748536 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2041,6 +2041,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (!isVisible()) { return; } + final WallpaperWindowToken wallpaperToken = mToken.asWallpaperToken(); + if (wallpaperToken != null) { + if (wallpaperToken.hasVisibleNotDrawnWallpaper()) { + outWaitingForDrawn.add(this); + } + return; + } if (mActivityRecord != null) { if (!mActivityRecord.isVisibleRequested()) return; if (mActivityRecord.allDrawn) { diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java index 424d50434010..6d5fc80b8d1e 100644 --- a/services/core/java/com/android/server/wm/WindowTracing.java +++ b/services/core/java/com/android/server/wm/WindowTracing.java @@ -155,13 +155,13 @@ class WindowTracing { logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush."); writeTraceToFileLocked(); logAndPrintln(pw, "Trace written to " + mTraceFile + "."); - if (!android.tracing.Flags.perfettoProtolog()) { + if (!android.tracing.Flags.perfettoProtologTracing()) { ((LegacyProtoLogImpl) mProtoLog).stopProtoLog(pw, true); } logAndPrintln(pw, "Start tracing to " + mTraceFile + "."); mBuffer.resetBuffer(); mEnabled = mEnabledLockFree = true; - if (!android.tracing.Flags.perfettoProtolog()) { + if (!android.tracing.Flags.perfettoProtologTracing()) { ((LegacyProtoLogImpl) mProtoLog).startProtoLog(pw); } } diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt index b9d89c2184b7..28889de4bbb1 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt @@ -347,7 +347,8 @@ object PermissionFlags { } fun isAppOpGranted(flags: Int): Boolean = - isPermissionGranted(flags) && !flags.hasBits(APP_OP_REVOKED) + isPermissionGranted(flags) && !flags.hasBits(RESTRICTION_REVOKED) && + !flags.hasBits(APP_OP_REVOKED) fun toApiFlags(flags: Int): Int { var apiFlags = 0 diff --git a/services/tests/VpnTests/Android.bp b/services/tests/VpnTests/Android.bp new file mode 100644 index 000000000000..6ad27fc1a167 --- /dev/null +++ b/services/tests/VpnTests/Android.bp @@ -0,0 +1,22 @@ +//######################################################################## +// Build FrameworksVpnTests package +//######################################################################## +package { + default_team: "trendy_team_fwk_core_networking", + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "Android-Apache-2.0" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test { + name: "FrameworksVpnTests", + srcs: [ + "java/**/*.java", + "java/**/*.kt", + ], + + test_suites: ["device-tests"], +} diff --git a/services/tests/VpnTests/AndroidManifest.xml b/services/tests/VpnTests/AndroidManifest.xml new file mode 100644 index 000000000000..d884084f2eb7 --- /dev/null +++ b/services/tests/VpnTests/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.tests.vpn"> + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.tests.vpn" + android:label="Frameworks VPN Tests" /> +</manifest>
\ No newline at end of file diff --git a/services/tests/VpnTests/AndroidTest.xml b/services/tests/VpnTests/AndroidTest.xml new file mode 100644 index 000000000000..ebeeac7d269b --- /dev/null +++ b/services/tests/VpnTests/AndroidTest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="Runs VPN Tests."> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="FrameworksVpnTests.apk" /> + </target_preparer> + + <option name="test-tag" value="FrameworksVpnTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.frameworks.tests.vpn" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration>
\ No newline at end of file diff --git a/services/tests/VpnTests/OWNERS b/services/tests/VpnTests/OWNERS new file mode 100644 index 000000000000..45ea251a02e4 --- /dev/null +++ b/services/tests/VpnTests/OWNERS @@ -0,0 +1,2 @@ +set noparent +file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java index 060f99b4317a..397d77c52f68 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java @@ -16,6 +16,8 @@ package com.android.server.display.brightness; +import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT; + import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE; import static org.junit.Assert.assertEquals; @@ -43,6 +45,7 @@ public final class BrightnessEventTest { getReason(BrightnessReason.REASON_DOZE, BrightnessReason.MODIFIER_LOW_POWER)); mBrightnessEvent.setPhysicalDisplayId("test"); mBrightnessEvent.setDisplayState(Display.STATE_ON); + mBrightnessEvent.setDisplayPolicy(POLICY_BRIGHT); mBrightnessEvent.setLux(100.0f); mBrightnessEvent.setPreThresholdLux(150.0f); mBrightnessEvent.setTime(System.currentTimeMillis()); @@ -74,11 +77,12 @@ public final class BrightnessEventTest { public void testToStringWorksAsExpected() { String actualString = mBrightnessEvent.toString(false); String expectedString = - "BrightnessEvent: disp=1, physDisp=test, displayState=ON, brt=0.6, initBrt=25.0," - + " rcmdBrt=0.6, preBrt=NaN, lux=100.0, preLux=150.0, hbmMax=0.62, hbmMode=off," - + " rbcStrength=-1, thrmMax=0.65, powerFactor=0.2, wasShortTermModelActive=true," - + " flags=, reason=doze [ low_pwr ], autoBrightness=true, strategy=" - + DISPLAY_BRIGHTNESS_STRATEGY_NAME + ", autoBrightnessMode=idle"; + "BrightnessEvent: disp=1, physDisp=test, displayState=ON, displayPolicy=BRIGHT," + + " brt=0.6, initBrt=25.0, rcmdBrt=0.6, preBrt=NaN, lux=100.0, preLux=150.0," + + " hbmMax=0.62, hbmMode=off, rbcStrength=-1, thrmMax=0.65, powerFactor=0.2," + + " wasShortTermModelActive=true, flags=, reason=doze [ low_pwr ]," + + " autoBrightness=true, strategy=" + DISPLAY_BRIGHTNESS_STRATEGY_NAME + + ", autoBrightnessMode=idle"; assertEquals(expectedString, actualString); } diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java index 5fe60d779fa6..b012aaaed3bf 100644 --- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java @@ -16,18 +16,26 @@ package com.android.server.contentprotection; +import static android.app.admin.DevicePolicyManager.CONTENT_PROTECTION_DISABLED; +import static android.app.admin.DevicePolicyManager.CONTENT_PROTECTION_ENABLED; +import static android.app.admin.DevicePolicyManager.CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY; +import static android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED; + import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import android.app.admin.DevicePolicyCache; import android.app.admin.DevicePolicyManagerInternal; -import android.content.ContentResolver; import android.net.Uri; import android.os.Handler; import android.os.Looper; -import android.os.UserHandle; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.testing.TestableContentResolver; import android.testing.TestableContext; @@ -36,6 +44,9 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.server.LocalServices; + +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -78,29 +89,36 @@ public class ContentProtectionConsentManagerTest { public final TestableContext mTestableContext = new TestableContext(ApplicationProvider.getApplicationContext()); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private final TestableContentResolver mTestableContentResolver = mTestableContext.getContentResolver(); - @Mock private ContentResolver mMockContentResolver; - @Mock private DevicePolicyManagerInternal mMockDevicePolicyManagerInternal; + @Mock private DevicePolicyCache mMockDevicePolicyCache; + + @Before + public void setup() { + setupLocalService(DevicePolicyManagerInternal.class, mMockDevicePolicyManagerInternal); + } + @Test - public void constructor_registersContentObserver() { + public void isConsentGranted_policyFlagDisabled_packageVerifierNotGranted() { + mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); ContentProtectionConsentManager manager = - createContentProtectionConsentManager(mMockContentResolver); + createContentProtectionConsentManager(VALUE_FALSE, VALUE_TRUE); + + boolean actual = manager.isConsentGranted(TEST_USER_ID); - assertThat(manager.mContentObserver).isNotNull(); - verify(mMockContentResolver) - .registerContentObserver( - URI_PACKAGE_VERIFIER_USER_CONSENT, - /* notifyForDescendants= */ false, - manager.mContentObserver, - UserHandle.USER_ALL); + assertThat(actual).isFalse(); + verifyZeroInteractions(mMockDevicePolicyManagerInternal); + verifyZeroInteractions(mMockDevicePolicyCache); } @Test - public void isConsentGranted_packageVerifierNotGranted() { + public void isConsentGranted_policyFlagEnabled_packageVerifierNotGranted() { + mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_FALSE, VALUE_TRUE); @@ -108,10 +126,12 @@ public class ContentProtectionConsentManagerTest { assertThat(actual).isFalse(); verifyZeroInteractions(mMockDevicePolicyManagerInternal); + verifyZeroInteractions(mMockDevicePolicyCache); } @Test - public void isConsentGranted_contentProtectionNotGranted() { + public void isConsentGranted_policyFlagDisabled_contentProtectionNotGranted() { + mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE, VALUE_FALSE); @@ -119,10 +139,52 @@ public class ContentProtectionConsentManagerTest { assertThat(actual).isFalse(); verifyZeroInteractions(mMockDevicePolicyManagerInternal); + verifyZeroInteractions(mMockDevicePolicyCache); + } + + @Test + public void isConsentGranted_policyFlagDisabled_packageVerifierGranted_userNotManaged() { + mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); + ContentProtectionConsentManager manager = + createContentProtectionConsentManager(VALUE_TRUE, VALUE_TRUE); + + boolean actual = manager.isConsentGranted(TEST_USER_ID); + + assertThat(actual).isTrue(); + verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); + verifyZeroInteractions(mMockDevicePolicyCache); + } + + @Test + public void isConsentGranted_policyFlagDisabled_packageVerifierGranted_userManaged() { + mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); + when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID)) + .thenReturn(true); + ContentProtectionConsentManager manager = + createContentProtectionConsentManager(VALUE_TRUE, VALUE_TRUE); + + boolean actual = manager.isConsentGranted(TEST_USER_ID); + + assertThat(actual).isFalse(); + verifyZeroInteractions(mMockDevicePolicyCache); + } + + @Test + public void isConsentGranted_policyFlagEnabled_packageVerifierGranted_userNotManaged_contentProtectionNotGranted() { + mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); + ContentProtectionConsentManager manager = + createContentProtectionConsentManager(VALUE_TRUE, VALUE_FALSE); + + boolean actual = manager.isConsentGranted(TEST_USER_ID); + + assertThat(actual).isFalse(); + verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); + verifyZeroInteractions(mMockDevicePolicyCache); } @Test - public void isConsentGranted_packageVerifierGranted_userNotManaged() { + public void isConsentGranted_policyFlagEnabled_packageVerifierGranted_userNotManaged_contentProtectionGranted() { + mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE, VALUE_TRUE); @@ -130,22 +192,110 @@ public class ContentProtectionConsentManagerTest { assertThat(actual).isTrue(); verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); + verifyZeroInteractions(mMockDevicePolicyCache); + } + + @Test + public void isConsentGranted_policyFlagEnabled_packageVerifierGranted_userManaged_policyDisabled() { + mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); + when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID)) + .thenReturn(true); + when(mMockDevicePolicyCache.getContentProtectionPolicy(TEST_USER_ID)) + .thenReturn(CONTENT_PROTECTION_DISABLED); + ContentProtectionConsentManager manager = + createContentProtectionConsentManager(VALUE_TRUE, VALUE_TRUE); + + boolean actual = manager.isConsentGranted(TEST_USER_ID); + + assertThat(actual).isFalse(); + verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); + verify(mMockDevicePolicyCache).getContentProtectionPolicy(TEST_USER_ID); + } + + @Test + public void isConsentGranted_policyFlagEnabled_packageVerifierGranted_userManaged_policyEnabled() { + mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); + when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID)) + .thenReturn(true); + when(mMockDevicePolicyCache.getContentProtectionPolicy(TEST_USER_ID)) + .thenReturn(CONTENT_PROTECTION_ENABLED); + ContentProtectionConsentManager manager = + createContentProtectionConsentManager(VALUE_TRUE, VALUE_FALSE); + + boolean actual = manager.isConsentGranted(TEST_USER_ID); + + assertThat(actual).isTrue(); + verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); + verify(mMockDevicePolicyCache).getContentProtectionPolicy(TEST_USER_ID); } @Test - public void isConsentGranted_packageVerifierGranted_userManaged() { + public void isConsentGranted_policyFlagEnabled_packageVerifierGranted_userManaged_policyNotControlled_contentProtectionGranted() { + mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID)) .thenReturn(true); + when(mMockDevicePolicyCache.getContentProtectionPolicy(TEST_USER_ID)) + .thenReturn(CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY); ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE, VALUE_TRUE); boolean actual = manager.isConsentGranted(TEST_USER_ID); + assertThat(actual).isTrue(); + verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); + verify(mMockDevicePolicyCache).getContentProtectionPolicy(TEST_USER_ID); + } + + @Test + public void isConsentGranted_policyFlagEnabled_packageVerifierGranted_userManaged_policyNotControlled_contentProtectionNotGranted() { + mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); + when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID)) + .thenReturn(true); + when(mMockDevicePolicyCache.getContentProtectionPolicy(TEST_USER_ID)) + .thenReturn(CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY); + ContentProtectionConsentManager manager = + createContentProtectionConsentManager(VALUE_TRUE, VALUE_FALSE); + + boolean actual = manager.isConsentGranted(TEST_USER_ID); + + assertThat(actual).isFalse(); + verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); + verify(mMockDevicePolicyCache).getContentProtectionPolicy(TEST_USER_ID); + } + + @Test + public void isConsentGranted_policyFlagEnabled_packageVerifierGranted_userManaged_policyNotControlled_contentProtectionDefault() { + mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); + when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID)) + .thenReturn(true); + when(mMockDevicePolicyCache.getContentProtectionPolicy(TEST_USER_ID)) + .thenReturn(CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY); + ContentProtectionConsentManager manager = + createContentProtectionConsentManager(VALUE_TRUE, VALUE_DEFAULT); + + boolean actual = manager.isConsentGranted(TEST_USER_ID); + + assertThat(actual).isTrue(); + verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); + verify(mMockDevicePolicyCache).getContentProtectionPolicy(TEST_USER_ID); + } + + @Test + public void isConsentGranted_policyFlagDisabled_packageVerifierDefault() { + mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); + ContentProtectionConsentManager manager = + createContentProtectionConsentManager(VALUE_DEFAULT, VALUE_TRUE); + + boolean actual = manager.isConsentGranted(TEST_USER_ID); + assertThat(actual).isFalse(); + verifyZeroInteractions(mMockDevicePolicyManagerInternal); + verifyZeroInteractions(mMockDevicePolicyCache); } @Test - public void isConsentGranted_packageVerifierDefault() { + public void isConsentGranted_policyFlagEnabled_packageVerifierDefault() { + mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_DEFAULT, VALUE_TRUE); @@ -153,10 +303,12 @@ public class ContentProtectionConsentManagerTest { assertThat(actual).isFalse(); verifyZeroInteractions(mMockDevicePolicyManagerInternal); + verifyZeroInteractions(mMockDevicePolicyCache); } @Test - public void isConsentGranted_contentProtectionDefault() { + public void isConsentGranted_policyFlagDisabled_contentProtectionDefault() { + mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE, VALUE_DEFAULT); @@ -164,57 +316,108 @@ public class ContentProtectionConsentManagerTest { assertThat(actual).isTrue(); verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); + verifyZeroInteractions(mMockDevicePolicyCache); } @Test - public void contentObserver_packageVerifier() { + public void contentObserver_policyFlagDisabled_packageVerifier() { + mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); ContentProtectionConsentManager manager = - createContentProtectionConsentManager(VALUE_TRUE, VALUE_DEFAULT); + createContentProtectionConsentManager(VALUE_FALSE, VALUE_TRUE); + boolean firstActual = manager.isConsentGranted(TEST_USER_ID); + assertThat(firstActual).isFalse(); + verify(mMockDevicePolicyManagerInternal, never()).isUserOrganizationManaged(anyInt()); - notifyContentObserver( - manager, - URI_PACKAGE_VERIFIER_USER_CONSENT, - KEY_PACKAGE_VERIFIER_USER_CONSENT, - VALUE_FALSE); + putGlobalSettings(KEY_PACKAGE_VERIFIER_USER_CONSENT, VALUE_TRUE); boolean secondActual = manager.isConsentGranted(TEST_USER_ID); - - assertThat(firstActual).isTrue(); assertThat(secondActual).isFalse(); + verify(mMockDevicePolicyManagerInternal, never()).isUserOrganizationManaged(anyInt()); + + notifyContentObserver(manager, URI_PACKAGE_VERIFIER_USER_CONSENT); + boolean thirdActual = manager.isConsentGranted(TEST_USER_ID); + assertThat(thirdActual).isTrue(); verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); + + verifyZeroInteractions(mMockDevicePolicyCache); } @Test - public void contentObserver_contentProtection() { + public void contentObserver_policyFlagEnabled_packageVerifier() { + mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); ContentProtectionConsentManager manager = - createContentProtectionConsentManager(VALUE_TRUE, VALUE_DEFAULT); + createContentProtectionConsentManager(VALUE_FALSE, VALUE_TRUE); + boolean firstActual = manager.isConsentGranted(TEST_USER_ID); + assertThat(firstActual).isFalse(); + verify(mMockDevicePolicyManagerInternal, never()).isUserOrganizationManaged(anyInt()); - notifyContentObserver( - manager, - URI_CONTENT_PROTECTION_USER_CONSENT, - KEY_CONTENT_PROTECTION_USER_CONSENT, - VALUE_FALSE); + putGlobalSettings(KEY_PACKAGE_VERIFIER_USER_CONSENT, VALUE_TRUE); boolean secondActual = manager.isConsentGranted(TEST_USER_ID); + assertThat(secondActual).isFalse(); + verify(mMockDevicePolicyManagerInternal, never()).isUserOrganizationManaged(anyInt()); - assertThat(firstActual).isTrue(); + notifyContentObserver(manager, URI_PACKAGE_VERIFIER_USER_CONSENT); + boolean thirdActual = manager.isConsentGranted(TEST_USER_ID); + assertThat(thirdActual).isTrue(); + verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); + + verifyZeroInteractions(mMockDevicePolicyCache); + } + + @Test + public void contentObserver_policyFlagDisabled_contentProtection() { + mSetFlagsRule.disableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); + ContentProtectionConsentManager manager = + createContentProtectionConsentManager(VALUE_TRUE, VALUE_FALSE); + + boolean firstActual = manager.isConsentGranted(TEST_USER_ID); + assertThat(firstActual).isFalse(); + verify(mMockDevicePolicyManagerInternal, never()).isUserOrganizationManaged(anyInt()); + + putGlobalSettings(KEY_CONTENT_PROTECTION_USER_CONSENT, VALUE_TRUE); + boolean secondActual = manager.isConsentGranted(TEST_USER_ID); assertThat(secondActual).isFalse(); + verify(mMockDevicePolicyManagerInternal, never()).isUserOrganizationManaged(anyInt()); + + notifyContentObserver(manager, URI_CONTENT_PROTECTION_USER_CONSENT); + boolean thirdActual = manager.isConsentGranted(TEST_USER_ID); + assertThat(thirdActual).isTrue(); + verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); + + verifyZeroInteractions(mMockDevicePolicyCache); + } + + @Test + public void contentObserver_policyFlagEnabled_contentProtection() { + mSetFlagsRule.enableFlags(FLAG_MANAGE_DEVICE_POLICY_ENABLED); + ContentProtectionConsentManager manager = + createContentProtectionConsentManager(VALUE_TRUE, VALUE_FALSE); + + boolean firstActual = manager.isConsentGranted(TEST_USER_ID); + assertThat(firstActual).isFalse(); verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID); + + putGlobalSettings(KEY_CONTENT_PROTECTION_USER_CONSENT, VALUE_TRUE); + boolean secondActual = manager.isConsentGranted(TEST_USER_ID); + assertThat(secondActual).isFalse(); + verify(mMockDevicePolicyManagerInternal, times(2)).isUserOrganizationManaged(TEST_USER_ID); + + notifyContentObserver(manager, URI_CONTENT_PROTECTION_USER_CONSENT); + boolean thirdActual = manager.isConsentGranted(TEST_USER_ID); + assertThat(thirdActual).isTrue(); + verify(mMockDevicePolicyManagerInternal, times(3)).isUserOrganizationManaged(TEST_USER_ID); + + verifyZeroInteractions(mMockDevicePolicyCache); } - private void notifyContentObserver( - ContentProtectionConsentManager manager, Uri uri, String key, int value) { + private void putGlobalSettings(String key, int value) { Settings.Global.putInt(mTestableContentResolver, key, value); - // Observer has to be called manually, mTestableContentResolver is not propagating - manager.mContentObserver.onChange(/* selfChange= */ false, uri, TEST_USER_ID); } - private ContentProtectionConsentManager createContentProtectionConsentManager( - ContentResolver contentResolver) { - return new ContentProtectionConsentManager( - new Handler(Looper.getMainLooper()), - contentResolver, - mMockDevicePolicyManagerInternal); + private void notifyContentObserver(ContentProtectionConsentManager manager, Uri uri) { + // Observer has to be called manually, mTestableContentResolver is not propagating + manager.mContentObserver.onChange(/* selfChange= */ false, uri, TEST_USER_ID); } private ContentProtectionConsentManager createContentProtectionConsentManager( @@ -227,6 +430,14 @@ public class ContentProtectionConsentManagerTest { mTestableContentResolver, KEY_CONTENT_PROTECTION_USER_CONSENT, valueContentProtectionUserConsent); - return createContentProtectionConsentManager(mTestableContentResolver); + return new ContentProtectionConsentManager( + new Handler(Looper.getMainLooper()), + mTestableContentResolver, + mMockDevicePolicyCache); + } + + private <T> void setupLocalService(Class<T> clazz, T service) { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService(clazz, service); } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index e59b5ea027ed..2ba3969bb9e5 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -48,7 +48,6 @@ import android.os.UserManager; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; import android.provider.Settings; -import android.security.KeyStore; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -102,7 +101,6 @@ public abstract class BaseLockSettingsServiceTests { IActivityManager mActivityManager; DevicePolicyManager mDevicePolicyManager; DevicePolicyManagerInternal mDevicePolicyManagerInternal; - KeyStore mKeyStore; MockSyntheticPasswordManager mSpManager; IAuthSecret mAuthSecretService; WindowManagerInternal mMockWindowManager; @@ -165,7 +163,6 @@ public abstract class BaseLockSettingsServiceTests { new LockSettingsServiceTestable.MockInjector( mContext, mStorage, - mKeyStore, mActivityManager, setUpStorageManagerMock(), mSpManager, diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java index 296d2cba83dd..f9077c4ae602 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java @@ -30,7 +30,6 @@ import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.os.storage.IStorageManager; -import android.security.KeyStore; import android.security.keystore.KeyPermanentlyInvalidatedException; import android.service.gatekeeper.IGateKeeperService; @@ -41,6 +40,7 @@ import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreMa import com.android.server.pm.UserManagerInternal; import java.io.FileNotFoundException; +import java.security.KeyStore; public class LockSettingsServiceTestable extends LockSettingsService { private Intent mSavedFrpNotificationIntent = null; @@ -50,7 +50,6 @@ public class LockSettingsServiceTestable extends LockSettingsService { public static class MockInjector extends LockSettingsService.Injector { private LockSettingsStorage mLockSettingsStorage; - private KeyStore mKeyStore; private IActivityManager mActivityManager; private IStorageManager mStorageManager; private SyntheticPasswordManager mSpManager; @@ -62,14 +61,13 @@ public class LockSettingsServiceTestable extends LockSettingsService { public boolean mIsHeadlessSystemUserMode = false; public boolean mIsMainUserPermanentAdmin = false; - public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore, - IActivityManager activityManager, - IStorageManager storageManager, SyntheticPasswordManager spManager, - FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager, + public MockInjector(Context context, LockSettingsStorage storage, + IActivityManager activityManager, IStorageManager storageManager, + SyntheticPasswordManager spManager, FakeGsiService gsiService, + RecoverableKeyStoreManager recoverableKeyStoreManager, UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) { super(context); mLockSettingsStorage = storage; - mKeyStore = keyStore; mActivityManager = activityManager; mStorageManager = storageManager; mSpManager = spManager; @@ -110,11 +108,6 @@ public class LockSettingsServiceTestable extends LockSettingsService { } @Override - public KeyStore getKeyStore() { - return mKeyStore; - } - - @Override public IStorageManager getStorageManager() { return mStorageManager; } @@ -145,8 +138,7 @@ public class LockSettingsServiceTestable extends LockSettingsService { } @Override - public UnifiedProfilePasswordCache getUnifiedProfilePasswordCache( - java.security.KeyStore ks) { + public UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(KeyStore ks) { return mock(UnifiedProfilePasswordCache.class); } diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java index 076d5caf5954..44d116181be5 100644 --- a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java +++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java @@ -147,7 +147,7 @@ public class AnrTimerTest { final int n = 4; StackTraceElement[] stack = Thread.currentThread().getStackTrace(); if (stack.length < n+1) return "test"; - return stack[n].getMethodName(); + return stack[n].getClassName() + "." + stack[n].getMethodName(); } } @@ -318,8 +318,11 @@ public class AnrTimerTest { public void testDumpOutput() throws Exception { if (!AnrTimer.nativeTimersSupported()) return; + // The timers in this class are named "class.method". + final String timerName = "timer: com.android.server.utils.AnrTimerTest"; + String r1 = getDumpOutput(); - assertThat(r1).doesNotContain("timer:"); + assertThat(r1).doesNotContain(timerName); Helper helper = new Helper(2); TestArg t1 = new TestArg(1, 1); @@ -333,14 +336,14 @@ public class AnrTimerTest { String r2 = getDumpOutput(); // There are timers in the list if and only if the feature is enabled. if (mEnabled) { - assertThat(r2).contains("timer:"); + assertThat(r2).contains(timerName); } else { - assertThat(r2).doesNotContain("timer:"); + assertThat(r2).doesNotContain(timerName); } } String r3 = getDumpOutput(); - assertThat(r3).doesNotContain("timer:"); + assertThat(r3).doesNotContain(timerName); } /** diff --git a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java index e27bb4c8c3b6..b9ece9360980 100644 --- a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java +++ b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java @@ -40,12 +40,19 @@ import java.util.concurrent.Executor; public class StubTransaction extends SurfaceControl.Transaction { private HashSet<Runnable> mWindowInfosReportedListeners = new HashSet<>(); + private HashSet<SurfaceControl.TransactionCommittedListener> mTransactionCommittedListeners = + new HashSet<>(); @Override public void apply() { for (Runnable listener : mWindowInfosReportedListeners) { listener.run(); } + for (SurfaceControl.TransactionCommittedListener listener + : mTransactionCommittedListeners) { + listener.onTransactionCommitted(); + } + mTransactionCommittedListeners.clear(); } @Override @@ -239,6 +246,9 @@ public class StubTransaction extends SurfaceControl.Transaction { @Override public SurfaceControl.Transaction addTransactionCommittedListener(Executor executor, SurfaceControl.TransactionCommittedListener listener) { + SurfaceControl.TransactionCommittedListener listenerInner = + () -> executor.execute(listener::onTransactionCommitted); + mTransactionCommittedListeners.add(listenerInner); return this; } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java new file mode 100644 index 000000000000..038f1db32d18 --- /dev/null +++ b/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2024 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.vibrator; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import android.util.IndentingPrintWriter; +import android.util.proto.ProtoOutputStream; + +import com.android.server.vibrator.GroupedAggregatedLogRecords.AggregatedLogRecord; +import com.android.server.vibrator.GroupedAggregatedLogRecords.SingleLogRecord; + +import org.junit.Test; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class GroupedAggregatedLogRecordsTest { + + private static final int AGGREGATION_TIME_LIMIT = 1000; + private static final int NO_AGGREGATION_TIME_LIMIT = 0; + private static final long PROTO_FIELD_ID = 1; + private static final int GROUP_1 = 1; + private static final int GROUP_2 = 2; + private static final int KEY_1 = 1; + private static final int KEY_2 = 2; + + private static final IndentingPrintWriter WRITER = new IndentingPrintWriter(new StringWriter()); + private static final ProtoOutputStream PROTO_OUTPUT_STREAM = new ProtoOutputStream(); + + private final List<TestSingleLogRecord> mTestRecords = new ArrayList<>(); + + @Test + public void record_noAggregation_keepsIndividualRecords() { + int sizeLimit = 10; + long createTime = 100; + TestGroupedAggregatedLogRecords records = new TestGroupedAggregatedLogRecords( + sizeLimit, NO_AGGREGATION_TIME_LIMIT, PROTO_FIELD_ID); + + for (int i = 0; i < sizeLimit; i++) { + assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime++))).isNull(); + } + + dumpRecords(records); + assertGroupHeadersWrittenOnce(records, GROUP_1); + assertRecordsInRangeWrittenOnce(0, sizeLimit); + } + + @Test + public void record_sizeLimit_dropsOldestEntriesForNewOnes() { + long createTime = 100; + TestGroupedAggregatedLogRecords records = new TestGroupedAggregatedLogRecords( + /* sizeLimit= */ 2, NO_AGGREGATION_TIME_LIMIT, PROTO_FIELD_ID); + + TestSingleLogRecord firstRecord = createRecord(GROUP_1, KEY_1, createTime++); + assertThat(records.add(firstRecord)).isNull(); + assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime++))).isNull(); + + // Adding third record drops first record + AggregatedLogRecord<TestSingleLogRecord> droppedRecord = + records.add(createRecord(GROUP_1, KEY_1, createTime++)); + assertThat(droppedRecord).isNotNull(); + assertThat(droppedRecord.getLatest()).isEqualTo(firstRecord); + + dumpRecords(records); + assertGroupHeadersWrittenOnce(records, GROUP_1); + assertRecordsInRangeNotWritten(0, 1); // First record not written + assertRecordsInRangeWrittenOnce(1, 3); // All newest records written + } + + @Test + public void record_timeAggregation_aggregatesCloseRecordAndPrintsOnlyFirstAndLast() { + long createTime = 100; + TestGroupedAggregatedLogRecords records = new TestGroupedAggregatedLogRecords( + /* sizeLimit= */ 1, AGGREGATION_TIME_LIMIT, PROTO_FIELD_ID); + + // No record dropped, all aggregated in a single entry + assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime))).isNull(); + assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime + 1))).isNull(); + assertThat(records.add(createRecord(GROUP_1, KEY_1, + createTime + AGGREGATION_TIME_LIMIT - 2))).isNull(); + assertThat(records.add(createRecord(GROUP_1, KEY_1, + createTime + AGGREGATION_TIME_LIMIT - 1))).isNull(); + + dumpRecords(records); + assertGroupHeadersWrittenOnce(records, GROUP_1); + assertRecordsInRangeWrittenOnce(0, 1); // Writes first record + assertRecordsInRangeNotWritten(1, 3); // Skips aggregated records in between + assertRecordsInRangeWrittenOnce(3, 4); // Writes last record + } + + @Test + public void record_differentGroups_recordsKeptSeparate() { + long createTime = 100; + TestGroupedAggregatedLogRecords records = new TestGroupedAggregatedLogRecords( + /* sizeLimit= */ 1, AGGREGATION_TIME_LIMIT, PROTO_FIELD_ID); + + // No record dropped, all kept in separate aggregated lists + assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime++))).isNull(); + assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime++))).isNull(); + assertThat(records.add(createRecord(GROUP_2, KEY_2, createTime++))).isNull(); + assertThat(records.add(createRecord(GROUP_2, KEY_2, createTime++))).isNull(); + + dumpRecords(records); + assertGroupHeadersWrittenOnce(records, GROUP_1, GROUP_2); + assertRecordsInRangeWrittenOnce(0, 4); + } + + @Test + public void record_sameGroupDifferentAggregationKeys_recordsNotAggregated() { + long createTime = 100; + TestGroupedAggregatedLogRecords records = new TestGroupedAggregatedLogRecords( + /* sizeLimit= */ 1, AGGREGATION_TIME_LIMIT, PROTO_FIELD_ID); + + assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime++))).isNull(); + + // Second record on same group with different key not aggregated, drops first record + AggregatedLogRecord<TestSingleLogRecord> droppedRecord = + records.add(createRecord(GROUP_1, KEY_2, createTime++)); + assertThat(droppedRecord).isNotNull(); + assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.getFirst()); + + dumpRecords(records); + assertGroupHeadersWrittenOnce(records, GROUP_1); + assertRecordsInRangeNotWritten(0, 1); // Skips first record that was dropped + assertRecordsInRangeWrittenOnce(1, 2); // Writes last record + } + + @Test + public void record_sameGroupAndAggregationKeysDistantTimes_recordsNotAggregated() { + long createTime = 100; + TestGroupedAggregatedLogRecords records = new TestGroupedAggregatedLogRecords( + /* sizeLimit= */ 1, AGGREGATION_TIME_LIMIT, PROTO_FIELD_ID); + + assertThat(records.add(createRecord(GROUP_1, KEY_1, createTime))).isNull(); + + // Second record after aggregation time limit not aggregated, drops first record + AggregatedLogRecord<TestSingleLogRecord> droppedRecord = + records.add(createRecord(GROUP_1, KEY_1, createTime + AGGREGATION_TIME_LIMIT)); + assertThat(droppedRecord).isNotNull(); + assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.getFirst()); + + dumpRecords(records); + assertGroupHeadersWrittenOnce(records, GROUP_1); + assertRecordsInRangeNotWritten(0, 1); // Skips first record that was dropped + assertRecordsInRangeWrittenOnce(1, 2); // Writes last record + } + + private TestSingleLogRecord createRecord(int groupKey, int aggregateKey, long createTime) { + TestSingleLogRecord record = new TestSingleLogRecord(groupKey, aggregateKey, createTime); + mTestRecords.add(record); + return record; + } + + private void dumpRecords(TestGroupedAggregatedLogRecords records) { + records.dump(WRITER); + records.dump(PROTO_OUTPUT_STREAM); + } + + private void assertGroupHeadersWrittenOnce(TestGroupedAggregatedLogRecords records, + int... groupKeys) { + assertThat(records.dumpGroupKeys).containsExactlyElementsIn( + Arrays.stream(groupKeys).boxed().toList()); + } + + private void assertRecordsInRangeWrittenOnce(int startIndexInclusive, int endIndexExclusive) { + for (int i = startIndexInclusive; i < endIndexExclusive; i++) { + assertWithMessage("record index=" + i).that(mTestRecords.get(i).dumpTextCount) + .isEqualTo(1); + assertWithMessage("record index=" + i).that(mTestRecords.get(i).dumpProtoFieldIds) + .containsExactly(PROTO_FIELD_ID); + } + } + + private void assertRecordsInRangeNotWritten(int startIndexInclusive, int endIndexExclusive) { + for (int i = startIndexInclusive; i < endIndexExclusive; i++) { + assertWithMessage("record index=" + i).that(mTestRecords.get(i).dumpTextCount) + .isEqualTo(0); + assertWithMessage("record index=" + i).that(mTestRecords.get(i).dumpProtoFieldIds) + .isEmpty(); + } + } + + private static final class TestGroupedAggregatedLogRecords + extends GroupedAggregatedLogRecords<TestSingleLogRecord> { + + public final List<Integer> dumpGroupKeys = new ArrayList<>(); + + private final long mProtoFieldId; + + TestGroupedAggregatedLogRecords(int sizeLimit, int aggregationTimeLimitMs, + long protoFieldId) { + super(sizeLimit, aggregationTimeLimitMs); + mProtoFieldId = protoFieldId; + } + + @Override + void dumpGroupHeader(IndentingPrintWriter pw, int groupKey) { + dumpGroupKeys.add(groupKey); + } + + @Override + long findGroupKeyProtoFieldId(int groupKey) { + return mProtoFieldId; + } + } + + private static final class TestSingleLogRecord implements SingleLogRecord { + public final List<Long> dumpProtoFieldIds = new ArrayList<>(); + public int dumpTextCount = 0; + + private final int mGroupKey; + private final int mAggregateKey; + private final long mCreateTime; + + TestSingleLogRecord(int groupKey, int aggregateKey, long createTime) { + mGroupKey = groupKey; + mAggregateKey = aggregateKey; + mCreateTime = createTime; + } + + @Override + public int getGroupKey() { + return mGroupKey; + } + + @Override + public long getCreateUptimeMs() { + return mCreateTime; + } + + @Override + public boolean mayAggregate(SingleLogRecord record) { + if (record instanceof TestSingleLogRecord param) { + return mAggregateKey == param.mAggregateKey; + } + return false; + } + + @Override + public void dump(IndentingPrintWriter pw) { + dumpTextCount++; + } + + @Override + public void dump(ProtoOutputStream proto, long fieldId) { + dumpProtoFieldIds.add(fieldId); + } + } +} diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java index 3d0dca0de87e..e3d45967848a 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java @@ -17,9 +17,11 @@ package com.android.server.vibrator; import static android.os.VibrationAttributes.CATEGORY_KEYBOARD; +import static android.os.VibrationAttributes.CATEGORY_UNKNOWN; import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE; +import static android.os.VibrationAttributes.USAGE_TOUCH; import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK; import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK; import static android.os.VibrationEffect.EFFECT_CLICK; @@ -285,7 +287,8 @@ public class HapticFeedbackVibrationProviderTest { HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( - SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ false); + SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ false, + false /* fromIme*/); assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isFalse(); } @@ -295,7 +298,7 @@ public class HapticFeedbackVibrationProviderTest { HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( - SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ true); + SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ true, false /* fromIme*/); assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isTrue(); } @@ -307,7 +310,7 @@ public class HapticFeedbackVibrationProviderTest { for (int effectId : SCROLL_FEEDBACK_CONSTANTS) { VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( - effectId, /* bypassVibrationIntensitySetting= */ false); + effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/); assertWithMessage("Expected FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId) .that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isTrue(); } @@ -320,40 +323,59 @@ public class HapticFeedbackVibrationProviderTest { for (int effectId : SCROLL_FEEDBACK_CONSTANTS) { VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( - effectId, /* bypassVibrationIntensitySetting= */ false); + effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/); assertWithMessage("Expected no FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId) .that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isFalse(); } } @Test - public void testVibrationAttribute_keyboardCategoryOff_notUseKeyboardCategory() { + public void testVibrationAttribute_keyboardCategoryOff_isIme_notUseKeyboardCategory() { mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( - effectId, /* bypassVibrationIntensitySetting= */ false); + effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/); + assertWithMessage("Expected USAGE_TOUCH for effect " + effectId) + .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH); assertWithMessage("Expected no CATEGORY_KEYBOARD for effect " + effectId) - .that(attrs.getCategory()).isEqualTo(0); + .that(attrs.getCategory()).isEqualTo(CATEGORY_UNKNOWN); } } @Test - public void testVibrationAttribute_keyboardCategoryOn_useKeyboardCategory() { + public void testVibrationAttribute_keyboardCategoryOn_notIme_notUseKeyboardCategory() { mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( - effectId, /* bypassVibrationIntensitySetting= */ false); + effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/); + assertWithMessage("Expected USAGE_TOUCH for effect " + effectId) + .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH); + assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId) + .that(attrs.getCategory()).isEqualTo(CATEGORY_UNKNOWN); + } + } + + @Test + public void testVibrationAttribute_keyboardCategoryOn_isIme_useKeyboardCategory() { + mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( + effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/); + assertWithMessage("Expected USAGE_TOUCH for effect " + effectId) + .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH); assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId) .that(attrs.getCategory()).isEqualTo(CATEGORY_KEYBOARD); } } @Test - public void testVibrationAttribute_noFixAmplitude_keyboardCategoryOn_noBypassIntensityScale() { + public void testVibrationAttribute_noFixAmplitude_notBypassIntensityScale() { mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); mockKeyboardVibrationFixedAmplitude(-1); @@ -361,7 +383,23 @@ public class HapticFeedbackVibrationProviderTest { for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( - effectId, /* bypassVibrationIntensitySetting= */ false); + effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/); + assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect " + + effectId) + .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse(); + } + } + + @Test + public void testVibrationAttribute_notIme_notBypassIntensityScale() { + mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); + mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); + mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE); + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( + effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/); assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect " + effectId) .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse(); @@ -369,7 +407,7 @@ public class HapticFeedbackVibrationProviderTest { } @Test - public void testVibrationAttribute_fixAmplitude_keyboardCategoryOn_bypassIntensityScale() { + public void testVibrationAttribute_fixAmplitude_isIme_bypassIntensityScale() { mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED); mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK); mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE); @@ -377,7 +415,7 @@ public class HapticFeedbackVibrationProviderTest { for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) { VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( - effectId, /* bypassVibrationIntensitySetting= */ false); + effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/); assertWithMessage("Expected FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect " + effectId) .that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isTrue(); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java index 3e59878f9e1e..b2644350dfdd 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java @@ -117,32 +117,32 @@ public class VibrationScalerTest { } @Test - public void testGetExternalVibrationScale() { + public void testGetScaleLevel() { setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH); assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_VERY_HIGH, - mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH)); + mVibrationScaler.getScaleLevel(USAGE_TOUCH)); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM); assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_HIGH, - mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH)); + mVibrationScaler.getScaleLevel(USAGE_TOUCH)); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW); assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_NONE, - mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH)); + mVibrationScaler.getScaleLevel(USAGE_TOUCH)); setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM); assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_LOW, - mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH)); + mVibrationScaler.getScaleLevel(USAGE_TOUCH)); setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_HIGH); assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_VERY_LOW, - mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH)); + mVibrationScaler.getScaleLevel(USAGE_TOUCH)); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); // Vibration setting being bypassed will use default setting and not scale. assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_NONE, - mVibrationScaler.getExternalVibrationScaleLevel(USAGE_TOUCH)); + mVibrationScaler.getScaleLevel(USAGE_TOUCH)); } @Test diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java index 0d5bf95d959d..3799abc100c9 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java @@ -38,10 +38,10 @@ import android.frameworks.vibrator.ScaleParam; import android.os.Binder; import android.os.Handler; import android.os.IBinder; -import android.os.RemoteException; import android.os.test.TestLooper; import android.util.SparseArray; +import androidx.test.InstrumentationRegistry; import androidx.test.core.app.ApplicationProvider; import com.android.internal.util.ArrayUtils; @@ -86,20 +86,20 @@ public class VibratorControlServiceTest { ApplicationProvider.getApplicationContext(), new Handler(testLooper.getLooper())); mFakeVibratorController = new FakeVibratorController(mTestLooper.getLooper()); - mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(), + mVibratorControlService = new VibratorControlService( + InstrumentationRegistry.getContext(), new VibratorControllerHolder(), mMockVibrationScaler, mVibrationSettings, mLock); } @Test - public void testRegisterVibratorController() throws RemoteException { + public void testRegisterVibratorController() { mVibratorControlService.registerVibratorController(mFakeVibratorController); assertThat(mFakeVibratorController.isLinkedToDeath).isTrue(); } @Test - public void testUnregisterVibratorController_providingTheRegisteredController_performsRequest() - throws RemoteException { + public void testUnregisterVibratorController_providingRegisteredController_performsRequest() { mVibratorControlService.registerVibratorController(mFakeVibratorController); mVibratorControlService.unregisterVibratorController(mFakeVibratorController); @@ -108,8 +108,7 @@ public class VibratorControlServiceTest { } @Test - public void testUnregisterVibratorController_providingAnInvalidController_ignoresRequest() - throws RemoteException { + public void testUnregisterVibratorController_providingAnInvalidController_ignoresRequest() { FakeVibratorController controller1 = new FakeVibratorController(mTestLooper.getLooper()); FakeVibratorController controller2 = new FakeVibratorController(mTestLooper.getLooper()); mVibratorControlService.registerVibratorController(controller1); @@ -120,8 +119,7 @@ public class VibratorControlServiceTest { } @Test - public void testOnRequestVibrationParamsComplete_cachesAdaptiveHapticsScalesCorrectly() - throws RemoteException { + public void testOnRequestVibrationParamsComplete_cachesAdaptiveHapticsScalesCorrectly() { mVibratorControlService.registerVibratorController(mFakeVibratorController); int timeoutInMillis = 10; CompletableFuture<Void> future = @@ -148,8 +146,7 @@ public class VibratorControlServiceTest { } @Test - public void testOnRequestVibrationParamsComplete_withIncorrectToken_ignoresRequest() - throws RemoteException, InterruptedException { + public void testOnRequestVibrationParamsComplete_withIncorrectToken_ignoresRequest() { mVibratorControlService.registerVibratorController(mFakeVibratorController); int timeoutInMillis = 10; CompletableFuture<Void> unusedFuture = @@ -167,8 +164,7 @@ public class VibratorControlServiceTest { } @Test - public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly() - throws RemoteException { + public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly() { mVibratorControlService.registerVibratorController(mFakeVibratorController); SparseArray<Float> vibrationScales = new SparseArray<>(); vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f); @@ -187,8 +183,7 @@ public class VibratorControlServiceTest { } @Test - public void testSetVibrationParams_withUnregisteredController_ignoresRequest() - throws RemoteException { + public void testSetVibrationParams_withUnregisteredController_ignoresRequest() { SparseArray<Float> vibrationScales = new SparseArray<>(); vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f); vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f); @@ -201,8 +196,7 @@ public class VibratorControlServiceTest { } @Test - public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales() - throws RemoteException { + public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales() { mVibratorControlService.registerVibratorController(mFakeVibratorController); int types = buildVibrationTypesMask(ScaleParam.TYPE_ALARM, ScaleParam.TYPE_NOTIFICATION); @@ -216,8 +210,7 @@ public class VibratorControlServiceTest { } @Test - public void testClearVibrationParams_withUnregisteredController_ignoresRequest() - throws RemoteException { + public void testClearVibrationParams_withUnregisteredController_ignoresRequest() { mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM, mFakeVibratorController); @@ -225,8 +218,7 @@ public class VibratorControlServiceTest { } @Test - public void testRequestVibrationParams_createsFutureRequestProperly() - throws RemoteException { + public void testRequestVibrationParams_createsFutureRequestProperly() { int timeoutInMillis = 10; mVibratorControlService.registerVibratorController(mFakeVibratorController); CompletableFuture<Void> future = @@ -243,8 +235,7 @@ public class VibratorControlServiceTest { } @Test - public void testShouldRequestVibrationParams_returnsTrueForVibrationsThatShouldRequestParams() - throws RemoteException { + public void testShouldRequestVibrationParams_returnsTrueForVibrationsThatShouldRequestParams() { int[] vibrations = new int[]{USAGE_ALARM, USAGE_RINGTONE, USAGE_MEDIA, USAGE_TOUCH, USAGE_NOTIFICATION, USAGE_HARDWARE_FEEDBACK, USAGE_UNKNOWN, USAGE_COMMUNICATION_REQUEST}; @@ -258,8 +249,7 @@ public class VibratorControlServiceTest { } @Test - public void testShouldRequestVibrationParams_unregisteredVibratorController_returnsFalse() - throws RemoteException { + public void testShouldRequestVibrationParams_unregisteredVibratorController_returnsFalse() { int[] vibrations = new int[]{USAGE_ALARM, USAGE_RINGTONE, USAGE_MEDIA, USAGE_TOUCH, USAGE_NOTIFICATION, USAGE_HARDWARE_FEEDBACK, USAGE_UNKNOWN, USAGE_COMMUNICATION_REQUEST}; @@ -269,7 +259,7 @@ public class VibratorControlServiceTest { } } - private int buildVibrationTypesMask(int... types) { + private static int buildVibrationTypesMask(int... types) { int typesMask = 0; for (int type : types) { typesMask |= type; diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index d2ad61f2ba9f..1ea90f55b727 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -2526,7 +2526,7 @@ public class VibratorManagerServiceTest { int constant, boolean always) throws InterruptedException { HalVibration vib = service.performHapticFeedbackInternal(UID, Context.DEVICE_ID_DEFAULT, PACKAGE_NAME, - constant, always, "some reason", service); + constant, always, "some reason", service, false /* fromIme */); if (vib != null) { vib.waitForEnd(); } diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java index 2a010f0a82a9..0cd88ef7a4b4 100644 --- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java +++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java @@ -60,11 +60,7 @@ public final class FakeVibratorController extends IVibratorController.Stub { requestTimeoutInMillis = timeoutInMillis; mHandler.post(() -> { if (mVibratorControlService != null) { - try { - mVibratorControlService.onRequestVibrationParamsComplete(token, mRequestResult); - } catch (RemoteException e) { - throw new RuntimeException(e); - } + mVibratorControlService.onRequestVibrationParamsComplete(token, mRequestResult); } }); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 67c528cf40ae..09e7b9141e04 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -527,7 +527,8 @@ public class ActivityRecordTests extends WindowTestsBase { // The configuration change is still sent to the activity, even if it doesn't relaunch. final ActivityConfigurationChangeItem expected = - ActivityConfigurationChangeItem.obtain(activity.token, newConfig); + ActivityConfigurationChangeItem.obtain(activity.token, newConfig, + activity.getActivityWindowInfo()); verify(mClientLifecycleManager).scheduleTransactionItem( eq(activity.app.getThread()), eq(expected)); } @@ -599,7 +600,8 @@ public class ActivityRecordTests extends WindowTestsBase { final Configuration currentConfig = activity.getConfiguration(); assertEquals(expectedOrientation, currentConfig.orientation); final ActivityConfigurationChangeItem expected = - ActivityConfigurationChangeItem.obtain(activity.token, currentConfig); + ActivityConfigurationChangeItem.obtain(activity.token, currentConfig, + activity.getActivityWindowInfo()); verify(mClientLifecycleManager).scheduleTransactionItem(activity.app.getThread(), expected); verify(displayRotation).onSetRequestedOrientation(); } @@ -818,7 +820,7 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityConfigurationChangeItem expected = ActivityConfigurationChangeItem.obtain(activity.token, - activity.getConfiguration()); + activity.getConfiguration(), activity.getActivityWindowInfo()); verify(mClientLifecycleManager).scheduleTransactionItem( activity.app.getThread(), expected); } finally { diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 0c1fbf3cb3d7..1a1fe95756d7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -94,6 +94,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { public void setUp() throws Exception { assumeFalse(WindowManagerService.sEnableShellTransitions); mAppTransitionController = new AppTransitionController(mWm, mDisplayContent); + mWm.mAnimator.ready(); } @Test @@ -855,7 +856,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); // Animation run by the remote handler. assertTrue(remoteAnimationRunner.isAnimationStarted()); @@ -886,7 +887,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, null /* changingTaskFragment */); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); // Animation is not run by the remote handler because the activity is filling the Task. assertFalse(remoteAnimationRunner.isAnimationStarted()); @@ -921,7 +922,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, null /* changingTaskFragment */); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); // Animation run by the remote handler. assertTrue(remoteAnimationRunner.isAnimationStarted()); @@ -946,7 +947,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); // Animation run by the remote handler. assertTrue(remoteAnimationRunner.isAnimationStarted()); @@ -973,7 +974,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); // Animation run by the remote handler. assertTrue(remoteAnimationRunner.isAnimationStarted()); @@ -997,7 +998,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); // Animation not run by the remote handler. assertFalse(remoteAnimationRunner.isAnimationStarted()); @@ -1024,7 +1025,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); // Animation should not run by the remote handler when there are non-embedded activities of // different UID. @@ -1051,7 +1052,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); // Animation should not run by the remote handler when there is wallpaper in the transition. assertFalse(remoteAnimationRunner.isAnimationStarted()); @@ -1085,7 +1086,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(activity1, null /* closingActivity */, taskFragment); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); // The animation will be animated remotely by client and all activities are input disabled // for untrusted animation. @@ -1136,7 +1137,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); // The animation will be animated remotely by client and all activities are input disabled // for untrusted animation. @@ -1178,7 +1179,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); // The animation will be animated remotely by client, but input should not be dropped for // fully trusted. diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 2085d6140f68..1f15ec3be3a8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -371,6 +371,7 @@ public class InsetsStateControllerTest extends WindowTestsBase { mDisplayContent.getInsetsPolicy().updateBarControlTarget(app); mDisplayContent.getInsetsPolicy().showTransient(statusBars(), true /* isGestureOnSystemBar */); + mWm.mAnimator.ready(); waitUntilWindowAnimatorIdle(); assertTrue(mDisplayContent.getInsetsPolicy().isTransient(statusBars())); diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 11d9629cf25e..a1638019359b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -43,7 +43,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -113,6 +112,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { runWithScissors(mWm.mH, () -> mHandler = new TestHandler(null, mClock), 0); mController = new RemoteAnimationController(mWm, mDisplayContent, mAdapter, mHandler, false /*isActivityEmbedding*/); + mWm.mAnimator.ready(); } private WindowState createAppOverlayWindow() { @@ -136,7 +136,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -168,7 +168,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -290,7 +290,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -336,7 +336,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { task.applyAnimationUnchecked(null /* lp */, true /* enter */, TRANSIT_OLD_TASK_OPEN, false /* isVoiceInteraction */, null /* sources */); mController.goodToGo(TRANSIT_OLD_TASK_OPEN); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); try { @@ -363,7 +363,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback); mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -417,7 +417,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback); mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -471,7 +471,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback); mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -526,7 +526,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -559,7 +559,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -595,7 +595,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -645,7 +645,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -782,7 +782,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mDisplayContent.applySurfaceChangesTransaction(); mController.goodToGo(TRANSIT_OLD_TASK_OPEN); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN), any(), any(), any(), any()); @@ -810,7 +810,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(transit); - mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + waitUntilWindowAnimatorIdle(); return adapter; } diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index a8f6fe86c823..7ab093d0ae13 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -546,7 +546,7 @@ public class SystemServicesTestRule implements TestRule { // This makes sure all previous messages in the handler are fully processed vs. just popping // them from the message queue. final AtomicBoolean currentMessagesProcessed = new AtomicBoolean(false); - wm.mAnimator.getChoreographer().postFrameCallback(time -> { + wm.mAnimator.addAfterPrepareSurfacesRunnable(() -> { synchronized (currentMessagesProcessed) { currentMessagesProcessed.set(true); currentMessagesProcessed.notifyAll(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index 7551b1650ad0..1233686a4b48 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -265,7 +265,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { @Override public boolean performHapticFeedback(int uid, String packageName, int effectId, - boolean always, String reason) { + boolean always, String reason, boolean fromIme) { return false; } diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 530a39e8b53e..0f2c62d3d09b 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -53,11 +53,14 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.service.usb.UsbServiceDumpProto; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.PackageMonitor; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; @@ -147,6 +150,7 @@ public class UsbService extends IUsbManager.Stub { private final UsbSettingsManager mSettingsManager; private final UsbPermissionManager mPermissionManager; + static final int PACKAGE_MONITOR_OPERATION_ID = 1; /** * The user id of the current user. There might be several profiles (with separate user ids) * per user. @@ -156,6 +160,10 @@ public class UsbService extends IUsbManager.Stub { private final Object mLock = new Object(); + // Key: USB port id + // Value: A set of UIDs of requesters who request disabling usb data + private final ArrayMap<String, ArraySet<Integer>> mUsbDisableRequesters = new ArrayMap<>(); + /** * @return the {@link UsbUserSettingsManager} for the given userId */ @@ -261,6 +269,10 @@ public class UsbService extends IUsbManager.Stub { if (mDeviceManager != null) { mDeviceManager.bootCompleted(); } + if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) { + new PackageUninstallMonitor() + .register(mContext, UserHandle.ALL, BackgroundThread.getHandler()); + } } /** Called when a user is unlocked. */ @@ -873,6 +885,11 @@ public class UsbService extends IUsbManager.Stub { Objects.requireNonNull(callback, "enableUsbData: callback must not be null. opId:" + operationId); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + + if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) { + if (!shouldUpdateUsbSignaling(portId, enable, Binder.getCallingUid())) return false; + } + final long ident = Binder.clearCallingIdentity(); boolean wait; try { @@ -892,6 +909,31 @@ public class UsbService extends IUsbManager.Stub { return wait; } + /** + * If enable = true, exclude UID from update list. + * If enable = false, include UID in update list. + * Return false if enable = true and the list is empty (no updates). + * Return true otherwise (let downstream decide on updates). + */ + private boolean shouldUpdateUsbSignaling(String portId, boolean enable, int uid) { + synchronized (mUsbDisableRequesters) { + if (!mUsbDisableRequesters.containsKey(portId)) { + mUsbDisableRequesters.put(portId, new ArraySet<>()); + } + + ArraySet<Integer> uidsOfDisableRequesters = mUsbDisableRequesters.get(portId); + + if (enable) { + uidsOfDisableRequesters.remove(uid); + // re-enable USB port (return true) if there are no other disable requesters + return uidsOfDisableRequesters.isEmpty(); + } else { + uidsOfDisableRequesters.add(uid); + } + } + return true; + } + @Override public void enableUsbDataWhileDocked(String portId, int operationId, IUsbOperationInternal callback) { @@ -1344,4 +1386,26 @@ public class UsbService extends IUsbManager.Stub { private static String removeLastChar(String value) { return value.substring(0, value.length() - 1); } + + /** + * Upon app removal, clear associated UIDs from the mUsbDisableRequesters list + * and re-enable USB data signaling if no remaining apps require USB disabling. + */ + private class PackageUninstallMonitor extends PackageMonitor { + @Override + public void onUidRemoved(int uid) { + synchronized (mUsbDisableRequesters) { + for (String portId : mUsbDisableRequesters.keySet()) { + ArraySet<Integer> disabledUid = mUsbDisableRequesters.get(portId); + if (disabledUid != null) { + disabledUid.remove(uid); + if (disabledUid.isEmpty()) { + enableUsbData(portId, true, PACKAGE_MONITOR_OPERATION_ID, + new IUsbOperationInternal.Default()); + } + } + } + } + } + } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index aef7158fd613..1e1dd00b8df5 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -181,10 +181,8 @@ public class VoiceInteractionManagerService extends SystemService { LocalServices.getService(ActivityManagerInternal.class)); mAtmInternal = Objects.requireNonNull( LocalServices.getService(ActivityTaskManagerInternal.class)); - mWmInternal = Objects.requireNonNull( - LocalServices.getService(WindowManagerInternal.class)); - mDpmInternal = Objects.requireNonNull( - LocalServices.getService(DevicePolicyManagerInternal.class)); + mWmInternal = LocalServices.getService(WindowManagerInternal.class); + mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class); LegacyPermissionManagerInternal permissionManagerInternal = LocalServices.getService( LegacyPermissionManagerInternal.class); permissionManagerInternal.setVoiceInteractionPackagesProvider( @@ -2750,11 +2748,17 @@ public class VoiceInteractionManagerService extends SystemService { if (isAssistDataAllowed) { visiblePackageNames.add(record.getComponentName().getPackageName()); } - if (mDpmInternal.isUserOrganizationManaged(record.getUserId())) { + if (mDpmInternal != null + && mDpmInternal.isUserOrganizationManaged(record.getUserId())) { isManagedProfileVisible = true; } } - final ScreenCapture.ScreenshotHardwareBuffer shb = mWmInternal.takeAssistScreenshot(); + final ScreenCapture.ScreenshotHardwareBuffer shb; + if (mWmInternal != null) { + shb = mWmInternal.takeAssistScreenshot(); + } else { + shb = null; + } final Bitmap bm = shb != null ? shb.asBitmap() : null; // Now that everything is fetched, putting it in the launchIntent. if (bm != null) { diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 626a2e574881..9d277c8fcf10 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -18535,7 +18535,7 @@ public class TelephonyManager { * @hide */ @SystemApi - @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) + @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_GET_LAST_KNOWN_CELL_IDENTITY) @RequiresPermission(allOf = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID}) public @Nullable CellIdentity getLastKnownCellIdentity() { diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java index b9f1738f9bb7..a96389046d6a 100644 --- a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java +++ b/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java @@ -37,7 +37,7 @@ import perfetto.protos.ProtologConfig; public class PerfettoDataSourceTest { @Before public void before() { - assumeTrue(android.tracing.Flags.perfettoProtolog()); + assumeTrue(android.tracing.Flags.perfettoProtologTracing()); } @Test diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt index 83e09bf7043e..fd7474b55fa6 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt @@ -20,7 +20,7 @@ import com.android.hoststubgen.asm.toJvmClassName /** * Filter to apply a policy to classes extending or implementing a class, - * either directly or indirectly. (with a breadth first search.) + * either directly or indirectly. * * The policy won't apply to the super class itself. */ @@ -42,7 +42,7 @@ class SubclassFilter( } /** - * Find a policy for a class with a breadth-first search. + * Find a policy for a class. */ private fun findPolicyForClass(className: String): FilterPolicyWithReason? { val cn = classes.findClass(className) ?: return null diff --git a/tools/protologtool/src/com/android/protolog/tool/Constants.kt b/tools/protologtool/src/com/android/protolog/tool/Constants.kt index aa3e00f2f4db..4a93de916826 100644 --- a/tools/protologtool/src/com/android/protolog/tool/Constants.kt +++ b/tools/protologtool/src/com/android/protolog/tool/Constants.kt @@ -18,7 +18,7 @@ package com.android.protolog.tool object Constants { const val NAME = "protologtool" - const val VERSION = "1.0.0" + const val VERSION = "2.0.0" const val IS_ENABLED_METHOD = "isEnabled" const val ENUM_VALUES_METHOD = "values" } |