diff options
76 files changed, 2321 insertions, 691 deletions
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/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/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/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/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/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/View.java b/core/java/android/view/View.java index a2faabc575c4..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; @@ -28376,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() { @@ -31427,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/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/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/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/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/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/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/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/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/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/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/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/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/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 b1823b4ee38f..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" 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/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/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/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/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/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/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/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 |