diff options
115 files changed, 2310 insertions, 472 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 1e4258191abf..6b5554bdd05a 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -20,7 +20,9 @@ java_defaults { srcs: [ ":android.app.usage.flags-aconfig-java{.generated_srcjars}", ":android.os.flags-aconfig-java{.generated_srcjars}", + ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}", ":android.security.flags-aconfig-java{.generated_srcjars}", + ":android.media.flags-aconfig-java{.generated_srcjars}", ":camera_platform_flags_core_java_lib{.generated_srcjars}", ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", ":com.android.text.flags-aconfig-java{.generated_srcjars}", @@ -80,6 +82,11 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +cc_aconfig_library { + name: "aconfig_text_flags_c_lib", + aconfig_declarations: "com.android.text.flags-aconfig", +} + // Security aconfig_declarations { name: "android.security.flags-aconfig", @@ -152,3 +159,29 @@ java_aconfig_library { aconfig_declarations: "android.view.inputmethod.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Vibrator +aconfig_declarations { + name: "android.os.vibrator.flags-aconfig", + package: "android.os.vibrator", + srcs: ["core/java/android/os/vibrator/*.aconfig"], +} + +java_aconfig_library { + name: "android.os.vibrator.flags-aconfig-java", + aconfig_declarations: "android.os.vibrator.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + +// Media +aconfig_declarations { + name: "android.media.flags-aconfig", + package: "android.media", + srcs: ["media/java/android/media/*.aconfig"], +} + +java_aconfig_library { + name: "android.media.flags-aconfig-java", + aconfig_declarations: "android.media.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java index 17076bc4eea4..66c1efca0000 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java @@ -419,6 +419,14 @@ public class PowerExemptionManager { */ public static final int REASON_SYSTEM_EXEMPT_APP_OP = 327; + /** + * Granted by {@link com.android.server.pm.PackageArchiverService} to the installer responsible + * for unarchiving an app. + * + * @hide + */ + public static final int REASON_PACKAGE_UNARCHIVE = 328; + /** @hide The app requests out-out. */ public static final int REASON_OPT_OUT_REQUESTED = 1000; @@ -502,6 +510,7 @@ public class PowerExemptionManager { REASON_ACTIVE_DEVICE_ADMIN, REASON_MEDIA_NOTIFICATION_TRANSFER, REASON_PACKAGE_INSTALLER, + REASON_PACKAGE_UNARCHIVE, }) @Retention(RetentionPolicy.SOURCE) public @interface ReasonCode {} diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 2fa31ed91201..0c619815ac78 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3213,6 +3213,7 @@ package android.companion.virtual { method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback); method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener); method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener); + method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback); } @@ -3546,6 +3547,7 @@ package android.content { field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS"; field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED"; field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED"; + field public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP"; field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED"; field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED"; @@ -3795,6 +3797,9 @@ package android.content.pm { public class PackageArchiver { method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException; + method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + field public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS"; + field public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME"; } public class PackageInfo implements android.os.Parcelable { diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 4801d155eb33..be699f4b9fa3 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -70,6 +70,12 @@ interface IVirtualDevice { void close(); /** + * Specifies a policy for this virtual device. + */ + @EnforcePermission("CREATE_VIRTUAL_DEVICE") + void setDevicePolicy(int policyType, int devicePolicy); + + /** * Notifies that an audio session being started. */ @EnforcePermission("CREATE_VIRTUAL_DEVICE") diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java index d13bfd4f6229..2e5c0f77de4a 100644 --- a/core/java/android/companion/virtual/VirtualDeviceInternal.java +++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java @@ -238,6 +238,15 @@ public class VirtualDeviceInternal { } } + void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType, + @VirtualDeviceParams.DevicePolicy int devicePolicy) { + try { + mVirtualDevice.setDevicePolicy(policyType, devicePolicy); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @NonNull VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) { try { diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index af4137057bf9..923e68951509 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -19,6 +19,7 @@ package android.companion.virtual; import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -59,6 +60,8 @@ import android.os.RemoteException; import android.util.Log; import android.view.Surface; +import com.android.internal.util.AnnotationValidations; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -513,6 +516,28 @@ public final class VirtualDeviceManager { } /** + * Specifies a policy for this virtual device. + * + * <p>Policies define the system behavior that may be specific for this virtual device. The + * given policy must be able to be changed dynamically during the lifetime of the device. + * + * @param policyType the type of policy, i.e. which behavior to specify a policy for. + * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior. + * + * @see VirtualDeviceParams#POLICY_TYPE_RECENTS + */ + @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType, + @VirtualDeviceParams.DevicePolicy int devicePolicy) { + AnnotationValidations.validate( + VirtualDeviceParams.DynamicPolicyType.class, null, policyType); + AnnotationValidations.validate( + VirtualDeviceParams.DevicePolicy.class, null, devicePolicy); + mVirtualDeviceInternal.setDevicePolicy(policyType, devicePolicy); + } + + /** * Creates a virtual dpad. * * @param config the configurations of the virtual dpad. diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index b6d837589284..037e814a722d 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -150,6 +150,17 @@ public final class VirtualDeviceParams implements Parcelable { public @interface PolicyType {} /** + * Policy types that can be dynamically changed during the virtual device's lifetime. + * + * @see VirtualDeviceManager.VirtualDevice#setDevicePolicy + * @hide + */ + @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_RECENTS}) + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) + public @interface DynamicPolicyType {} + + /** * Tells the sensor framework how to handle sensor requests from contexts associated with this * virtual device, namely the sensors returned by * {@link android.hardware.SensorManager#getSensorList}: @@ -375,6 +386,14 @@ public final class VirtualDeviceParams implements Parcelable { } /** + * Returns all device policies. + * @hide + */ + public @NonNull SparseIntArray getDevicePolicies() { + return mDevicePolicies; + } + + /** * Returns the configurations for all sensors that should be created for this device. * * @see Builder#addVirtualSensorConfig diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index 39b99c690518..057b8565a8bb 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -6,3 +6,10 @@ flag { description: "More logs to test flags with" bug: "291725823" } + +flag { + name: "dynamic_policy" + namespace: "virtual_devices" + description: "Enable dynamic policy API" + bug: "298401780" +} diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 31f6418ac3d0..fe7d1e6fb232 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -5270,6 +5270,16 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_SHOW_FOREGROUND_SERVICE_MANAGER = "android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER"; + /** + * Broadcast Action: Sent to the responsible installer of an archived package when unarchival + * is requested. + * + * @see android.content.pm.PackageArchiver + * @hide + */ + @SystemApi + public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). diff --git a/core/java/android/content/pm/IPackageArchiverService.aidl b/core/java/android/content/pm/IPackageArchiverService.aidl index fc471c451370..dc6491d6d97e 100644 --- a/core/java/android/content/pm/IPackageArchiverService.aidl +++ b/core/java/android/content/pm/IPackageArchiverService.aidl @@ -23,4 +23,7 @@ interface IPackageArchiverService { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})") void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})") + void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle); }
\ No newline at end of file diff --git a/core/java/android/content/pm/PackageArchiver.java b/core/java/android/content/pm/PackageArchiver.java index d739d5054800..b06523160b92 100644 --- a/core/java/android/content/pm/PackageArchiver.java +++ b/core/java/android/content/pm/PackageArchiver.java @@ -42,6 +42,26 @@ import android.os.RemoteException; @SystemApi public class PackageArchiver { + /** + * Extra field for the package name of a package that is requested to be unarchived. Sent as + * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent. + * + * @hide + */ + @SystemApi + public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = + "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME"; + + /** + * If true, the requestor of the unarchival has specified that the app should be unarchived + * for {@link android.os.UserHandle#ALL}. + * + * @hide + */ + @SystemApi + public static final String EXTRA_UNARCHIVE_ALL_USERS = + "android.content.pm.extra.UNARCHIVE_ALL_USERS"; + private final Context mContext; private final IPackageArchiverService mService; @@ -58,7 +78,7 @@ public class PackageArchiver { * * @param statusReceiver Callback used to notify when the operation is completed. * @throws NameNotFoundException If {@code packageName} isn't found or not available to the - * caller. + * caller or isn't archived. * @hide */ @RequiresPermission(anyOf = { @@ -76,4 +96,34 @@ public class PackageArchiver { throw e.rethrowFromSystemServer(); } } + + /** + * Requests to unarchive a currently archived package. + * + * <p> Sends a request to unarchive an app to the responsible installer. The installer is + * determined by {@link InstallSourceInfo#getUpdateOwnerPackageName()}, or + * {@link InstallSourceInfo#getInstallingPackageName()} if the former value is null. + * + * <p> The installation will happen asynchronously and can be observed through + * {@link android.content.Intent#ACTION_PACKAGE_ADDED}. + * + * @throws NameNotFoundException If {@code packageName} isn't found or not visible to the + * caller or if the package has no installer on the device + * anymore to unarchive it. + * @hide + */ + @RequiresPermission(anyOf = { + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.REQUEST_INSTALL_PACKAGES}) + @SystemApi + public void requestUnarchive(@NonNull String packageName) + throws NameNotFoundException { + try { + mService.requestUnarchive(packageName, mContext.getPackageName(), mContext.getUser()); + } catch (ParcelableException e) { + e.maybeRethrow(NameNotFoundException.class); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index c01664e55744..8be4c5858694 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -237,7 +237,7 @@ final class NavigationBarController { mNavigationBarFrame.setOnApplyWindowInsetsListener((view, insets) -> { if (mNavigationBarFrame != null) { boolean visible = insets.isVisible(captionBar()); - mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); + mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE); } return view.onApplyWindowInsets(insets); }); diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl index 62753527929c..f30dd20d7087 100644 --- a/core/java/android/os/IVibratorManagerService.aidl +++ b/core/java/android/os/IVibratorManagerService.aidl @@ -36,4 +36,10 @@ interface IVibratorManagerService { void vibrate(int uid, int displayId, String opPkg, in CombinedVibration vibration, in VibrationAttributes attributes, String reason, IBinder token); void cancelVibrate(int usageFilter, IBinder token); + + // Async oneway APIs. + // There is no order guarantee with respect to the two-way APIs above like + // vibrate/isVibrating/cancel. + oneway void performHapticFeedback(int uid, int displayId, String opPkg, int constant, + boolean always, String reason, IBinder token); } diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index 1cd0f3b156c2..04c257b92e29 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -206,6 +206,15 @@ public class SystemVibrator extends Vibrator { } @Override + public void performHapticFeedback(int constant, boolean always, String reason) { + if (mVibratorManager == null) { + Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager."); + return; + } + mVibratorManager.performHapticFeedback(constant, always, reason); + } + + @Override public void cancel() { if (mVibratorManager == null) { Log.w(TAG, "Failed to cancel vibrate; no vibrator manager."); diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java index 284b2464c468..ee90834c15ef 100644 --- a/core/java/android/os/SystemVibratorManager.java +++ b/core/java/android/os/SystemVibratorManager.java @@ -145,6 +145,21 @@ public class SystemVibratorManager extends VibratorManager { } @Override + public void performHapticFeedback(int constant, boolean always, String reason) { + if (mService == null) { + Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager service."); + return; + } + try { + mService.performHapticFeedback( + Process.myUid(), mContext.getAssociatedDisplayId(), mPackageName, constant, + always, reason, mToken); + } catch (RemoteException e) { + Log.w(TAG, "Failed to perform haptic feedback.", e); + } + } + + @Override public void cancel() { cancelVibration(VibrationAttributes.USAGE_FILTER_MATCH_ALL); } @@ -228,6 +243,11 @@ public class SystemVibratorManager extends VibratorManager { } @Override + public void performHapticFeedback(int effectId, boolean always, String reason) { + SystemVibratorManager.this.performHapticFeedback(effectId, always, reason); + } + + @Override public void cancel() { SystemVibratorManager.this.cancel(); } diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index aafa5018af10..99c9925d9cb7 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -510,6 +510,28 @@ public abstract class Vibrator { String reason, @NonNull VibrationAttributes attributes); /** + * Performs a haptic feedback. + * + * <p>A haptic feedback is a short vibration feedback. The type of feedback is identified via + * the {@code constant}, which should be one of the effect constants provided in + * {@link HapticFeedbackConstants}. The haptic feedback provided for a given effect ID is + * consistent across all usages on the same device. + * + * @param constant the ID for the haptic feedback. This should be one of the constants defined + * in {@link HapticFeedbackConstants}. + * @param always {@code true} if the haptic feedback should be played regardless of the user + * vibration intensity settings applicable to the corresponding vibration. + * {@code false} if the vibration for the haptic feedback should respect the applicable + * vibration intensity settings. + * @param reason the reason for this haptic feedback. + * + * @hide + */ + public void performHapticFeedback(int constant, boolean always, String reason) { + Log.w(TAG, "performHapticFeedback is not supported"); + } + + /** * Query whether the vibrator natively supports the given effects. * * <p>If an effect is not supported, the system may still automatically fall back to playing diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java index f506ef8955d4..e0b6a9fd28f0 100644 --- a/core/java/android/os/VibratorManager.java +++ b/core/java/android/os/VibratorManager.java @@ -35,7 +35,8 @@ import android.util.Log; public abstract class VibratorManager { private static final String TAG = "VibratorManager"; - private final String mPackageName; + /** @hide */ + protected final String mPackageName; /** * @hide to prevent subclassing from outside of the framework @@ -137,6 +138,21 @@ public abstract class VibratorManager { String reason, @Nullable VibrationAttributes attributes); /** + * Performs a haptic feedback. + * + * @param constant the ID of the requested haptic feedback. Should be one of the constants + * defined in {@link HapticFeedbackConstants}. + * @param always {@code true} if the haptic feedback should be played regardless of the user + * vibration intensity settings applicable to the corresponding vibration. + * {@code false} otherwise. + * @param reason the reason for this haptic feedback. + * @hide + */ + public void performHapticFeedback(int constant, boolean always, String reason) { + Log.w(TAG, "performHapticFeedback is not supported"); + } + + /** * Turn all the vibrators off. */ @RequiresPermission(android.Manifest.permission.VIBRATE) diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig new file mode 100644 index 000000000000..361e244ac876 --- /dev/null +++ b/core/java/android/os/vibrator/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.os.vibrator" + +flag { + namespace: "vibrator" + name: "use_vibrator_haptic_feedback" + description: "Enables performHapticFeedback to directly use the vibrator service instead of going through the window session" + bug: "295459081" +}
\ No newline at end of file diff --git a/core/java/android/text/flags/phrase_strict_fallback.aconfig b/core/java/android/text/flags/phrase_strict_fallback.aconfig new file mode 100644 index 000000000000..c67a21bca0b4 --- /dev/null +++ b/core/java/android/text/flags/phrase_strict_fallback.aconfig @@ -0,0 +1,8 @@ +package: "com.android.text.flags" + +flag { + name: "phrase_strict_fallback" + namespace: "text" + description: "Feature flag for automatic fallback from phrase based line break to strict line break." + bug: "281970875" +} diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 5fe2aa1f5295..12527e91a87c 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -254,7 +254,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true"); DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true"); DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false"); - DEFAULT_FLAGS.put("settings_press_hold_nav_handle_to_search", "false"); // TODO: b/298454866 Replace with Trunk Stable Feature Flag DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false"); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 9a4cb7298ee6..30fd2cfa5f28 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -105,6 +105,8 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; +import android.os.Vibrator; +import android.os.vibrator.Flags; import android.sysprop.DisplayProperties; import android.text.InputType; import android.text.TextUtils; @@ -5411,6 +5413,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private PointerIcon mMousePointerIcon; + /** Vibrator for haptic feedback. */ + private Vibrator mVibrator; + /** * @hide */ @@ -27758,8 +27763,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, && !isHapticFeedbackEnabled()) { return false; } - return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, - (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0); + + final boolean always = (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0; + if (Flags.useVibratorHapticFeedback()) { + if (!mAttachInfo.canPerformHapticFeedback()) { + return false; + } + getSystemVibrator().performHapticFeedback( + feedbackConstant, always, "View#performHapticFeedback"); + return true; + } + return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, always); + } + + private Vibrator getSystemVibrator() { + if (mVibrator != null) { + return mVibrator; + } + return mVibrator = mContext.getSystemService(Vibrator.class); } /** @@ -31239,6 +31260,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return events; } + private boolean canPerformHapticFeedback() { + return mSession != null + && (mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) == 0; + } + @Nullable ScrollCaptureInternal getScrollCaptureInternal() { if (mScrollCaptureInternal != null) { diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 560e41b1aa33..b8d251fc5cc5 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -8,3 +8,10 @@ flag { description: "Whether the feature to sync different window-related config updates is enabled" bug: "260873529" } + +flag { + namespace: "windowing_sdk" + name: "activity_embedding_overlay_presentation_flag" + description: "Whether the overlay presentation feature is enabled" + bug: "243518738" +} diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml index d1a4935633cb..37d2fa0540f0 100644 --- a/core/res/res/layout/autofill_fill_dialog.xml +++ b/core/res/res/layout/autofill_fill_dialog.xml @@ -85,14 +85,18 @@ android:layout_marginStart="24dp" android:layout_marginEnd="24dp" android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton" - android:orientation="horizontal"> + android:orientation="horizontal" + android:gravity="center_vertical"> <Button android:id="@+id/autofill_dialog_no" android:layout_width="wrap_content" - android:layout_height="36dp" - android:layout_marginTop="6dp" - android:layout_marginBottom="6dp" + android:layout_height="40dp" + android:paddingStart="12dp" + android:paddingEnd="12dp" + android:paddingTop="0dp" + android:paddingBottom="0dp" + android:minWidth="0dp" style="?android:attr/borderlessButtonStyle" android:text="@string/autofill_save_no"> </Button> @@ -107,9 +111,8 @@ <Button android:id="@+id/autofill_dialog_yes" android:layout_width="wrap_content" - android:layout_height="36dp" - android:layout_marginTop="6dp" - android:layout_marginBottom="6dp" + android:layout_height="40dp" + android:minWidth="0dp" style="@style/AutofillHalfSheetTonalButton" android:text="@string/autofill_save_yes" android:visibility="gone" > diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml index 85529d6a4b3b..bed19a87eb16 100644 --- a/core/res/res/layout/autofill_save.xml +++ b/core/res/res/layout/autofill_save.xml @@ -72,14 +72,18 @@ android:layout_marginTop="32dp" android:layout_marginBottom="18dp" android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton" - android:orientation="horizontal"> + android:orientation="horizontal" + android:gravity="center_vertical"> <Button android:id="@+id/autofill_save_no" android:layout_width="wrap_content" - android:layout_height="36dp" - android:layout_marginTop="6dp" - android:layout_marginBottom="6dp" + android:layout_height="40dp" + android:paddingStart="12dp" + android:paddingEnd="12dp" + android:paddingTop="0dp" + android:paddingBottom="0dp" + android:minWidth="0dp" style="?android:attr/borderlessButtonStyle" android:text="@string/autofill_save_no"> </Button> @@ -94,9 +98,8 @@ <Button android:id="@+id/autofill_save_yes" android:layout_width="wrap_content" - android:layout_height="36dp" - android:layout_marginTop="6dp" - android:layout_marginBottom="6dp" + android:layout_height="40dp" + android:minWidth="0dp" style="@style/AutofillHalfSheetTonalButton" android:text="@string/autofill_save_yes"> </Button> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 03480e42e5ac..302c7fad8173 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5480,6 +5480,13 @@ of known compatibility issues. --> <string-array name="config_highRefreshRateBlacklist"></string-array> + <!-- The list of packages to automatically opt in to refresh rate suppressing by small area + detection. Format of this array should be packageName:threshold and threshold value should + be between 0 to 1--> + <string-array name="config_smallAreaDetectionAllowlist" translatable="false"> + <!-- Add packages:threshold here --> + </string-array> + <!-- The list of packages to force slowJpegMode for Apps using Camera API1 --> <string-array name="config_forceSlowJpegModeList" translatable="false"> <!-- Add packages here --> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 164f713200ac..13d04e53b508 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1500,12 +1500,8 @@ please see styles_device_defaults.xml. <item name="fontFamily">google-sans-text-medium</item> <item name="textStyle">normal</item> <item name="textAllCaps">false</item> - <item name="layout_marginTop">6dp</item> - <item name="layout_marginBottom">6dp</item> - <item name="paddingStart">16dp</item> - <item name="paddingEnd">16dp</item> - <item name="paddingTop">8dp</item> - <item name="paddingBottom">8dp</item> + <item name="paddingStart">24dp</item> + <item name="paddingEnd">24dp</item> </style> <!-- @hide Tonal button for Autofill half screen dialog --> <style name="AutofillHalfSheetTonalButton" parent="AutofillHalfSheetButton"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7ea597420746..02209a7ff86b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4288,6 +4288,8 @@ <java-symbol type="array" name="config_highRefreshRateBlacklist" /> <java-symbol type="array" name="config_forceSlowJpegModeList" /> + <java-symbol type="array" name="config_smallAreaDetectionAllowlist" /> + <java-symbol type="layout" name="chooser_dialog" /> <java-symbol type="layout" name="chooser_dialog_item" /> <java-symbol type="drawable" name="chooser_dialog_background" /> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index a41fb64716e2..0778311e98bd 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -67,7 +67,6 @@ import android.view.View; import android.window.WindowContextInfo; import android.window.WindowTokenClientController; -import androidx.test.filters.FlakyTest; import androidx.test.filters.MediumTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; @@ -254,15 +253,9 @@ public class ActivityThreadTest { // Execute a local relaunch item with current scaled config (e.g. simulate recreate), // the config should not be scaled again. - final Configuration currentConfig = activity.getResources().getConfiguration(); - final ClientTransaction localTransaction = - newTransaction(activityThread, activity.getActivityToken()); - localTransaction.addCallback(ActivityRelaunchItem.obtain( - null /* pendingResults */, null /* pendingIntents */, 0 /* configChanges */, - new MergedConfiguration(currentConfig, currentConfig), - true /* preserveWindow */)); InstrumentationRegistry.getInstrumentation().runOnMainSync( - () -> activityThread.executeTransaction(localTransaction)); + () -> activityThread.executeTransaction( + newRelaunchResumeTransaction(activity))); assertScreenScale(scale, activity, originalActivityConfig, originalActivityMetrics); } finally { @@ -630,7 +623,6 @@ public class ActivityThreadTest { }); } - @FlakyTest(bugId = 298331121) @Test public void testHandleConfigurationChanged_DoesntOverrideActivityConfig() { final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 9aac694e41bf..04795768aefc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -103,7 +103,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {} /** Whether this task listener supports compat UI. */ default boolean supportCompatUI() { - // All TaskListeners should support compat UI except PIP. + // All TaskListeners should support compat UI except PIP and StageCoordinator. return true; } /** Attaches a child window surface to the task surface. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java index 72702e7c2b88..b828aac39040 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java @@ -18,6 +18,7 @@ package com.android.wm.shell.common; import android.annotation.Nullable; import android.os.RemoteException; +import android.os.Trace; import android.util.Slog; import android.view.IDisplayChangeWindowCallback; import android.view.IDisplayChangeWindowController; @@ -40,6 +41,7 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public class DisplayChangeController { private static final String TAG = DisplayChangeController.class.getSimpleName(); + private static final String HANDLE_DISPLAY_CHANGE_TRACE_TAG = "HandleRemoteDisplayChange"; private final ShellExecutor mMainExecutor; private final IWindowManager mWmService; @@ -81,9 +83,15 @@ public class DisplayChangeController { /** Query all listeners for changes that should happen on display change. */ void dispatchOnDisplayChange(WindowContainerTransaction outWct, int displayId, int fromRotation, int toRotation, DisplayAreaInfo newDisplayAreaInfo) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.beginSection("dispatchOnDisplayChange"); + } for (OnDisplayChangingListener c : mDisplayChangeListener) { c.onDisplayChange(displayId, fromRotation, toRotation, newDisplayAreaInfo, outWct); } + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.endSection(); + } } private void onDisplayChange(int displayId, int fromRotation, int toRotation, @@ -94,6 +102,10 @@ public class DisplayChangeController { callback.continueDisplayChange(t); } catch (RemoteException e) { Slog.e(TAG, "Failed to continue handling display change", e); + } finally { + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.endAsyncSection(HANDLE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode()); + } } } @@ -103,6 +115,9 @@ public class DisplayChangeController { @Override public void onDisplayChange(int displayId, int fromRotation, int toRotation, DisplayAreaInfo newDisplayAreaInfo, IDisplayChangeWindowCallback callback) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.beginAsyncSection(HANDLE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode()); + } mMainExecutor.execute(() -> DisplayChangeController.this .onDisplayChange(displayId, fromRotation, toRotation, newDisplayAreaInfo, callback)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java index cbff4640239e..77aefc8f7e4a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java @@ -208,7 +208,7 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract } private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) { - return taskInfo.topActivityEligibleForUserAspectRatioButton + return taskInfo.topActivityEligibleForUserAspectRatioButton && (taskInfo.topActivityBoundsLetterboxed || taskInfo.isUserFullscreenOverrideEnabled); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 842b1bf9e8af..94fa485efd5c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -228,6 +228,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private final Toast mSplitUnsupportedToast; private SplitRequest mSplitRequest; + /** + * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support + * CompatUI layouts. CompatUI is handled separately by MainStage and SideStage. + */ + @Override + public boolean supportCompatUI() { + return false; + } + class SplitRequest { @SplitPosition int mActivatePosition; diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index 434b008a8e83..eb650caf04e2 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -126,6 +126,7 @@ java_defaults { "flickertestapplib", "flickerlib", "flickerlib-helpers", + "flickerlib-trace_processor_shell", "platform-test-annotations", "wm-flicker-common-app-helpers", "wm-flicker-common-assertions", diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml index 6a87de47def4..ae130b8f6f7d 100644 --- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml @@ -45,9 +45,13 @@ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" /> <!-- Enable bubble notification--> <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> + <!-- Allow the test to connect to perfetto trace processor --> + <uses-permission android:name="android.permission.INTERNET"/> - <!-- Allow the test to write directly to /sdcard/ --> - <application android:requestLegacyExternalStorage="true" android:largeHeap="true"> + <!-- Allow the test to write directly to /sdcard/ and connect to trace processor --> + <application android:requestLegacyExternalStorage="true" + android:networkSecurityConfig="@xml/network_security_config" + android:largeHeap="true"> <uses-library android:name="android.test.runner"/> <service android:name=".NotificationListener" diff --git a/libs/WindowManager/Shell/tests/flicker/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/flicker/res/xml/network_security_config.xml new file mode 100644 index 000000000000..4bd9ca049f55 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/res/xml/network_security_config.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<network-security-config> + <domain-config cleartextTrafficPermitted="true"> + <domain includeSubdomains="true">localhost</domain> + </domain-config> +</network-security-config> diff --git a/media/java/android/media/flags.aconfig b/media/java/android/media/flags.aconfig new file mode 100644 index 000000000000..8567a3b166ab --- /dev/null +++ b/media/java/android/media/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.media" + +flag { + name: "haptics_customization_enabled" + namespace: "media" + description: "Enables the haptics customization feature" + bug: "241918098" +} diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 3d35bad68a73..5dcb9d2f6e77 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -8,7 +8,6 @@ package { } android_library { - name: "SettingsLib", static_libs: [ @@ -26,44 +25,20 @@ android_library { "iconloader", "WifiTrackerLibRes", - "SettingsLibHelpUtils", - "SettingsLibRestrictedLockUtils", - "SettingsLibActionBarShadow", - "SettingsLibAppPreference", - "SettingsLibSearchWidget", - "SettingsLibSettingsSpinner", - "SettingsLibIllustrationPreference", - "SettingsLibLayoutPreference", - "SettingsLibMainSwitchPreference", - "SettingsLibActionButtonsPreference", - "SettingsLibEntityHeaderWidgets", - "SettingsLibBarChartPreference", - "SettingsLibProgressBar", - "SettingsLibAdaptiveIcon", - "SettingsLibRadioButtonPreference", - "SettingsLibSelectorWithWidgetPreference", + "SettingsLibDeviceStateRotationLock", "SettingsLibDisplayUtils", - "SettingsLibUtils", "SettingsLibEmergencyNumber", - "SettingsLibTopIntroPreference", - "SettingsLibBannerMessagePreference", - "SettingsLibFooterPreference", - "SettingsLibUsageProgressBarPreference", - "SettingsLibCollapsingToolbarBaseActivity", - "SettingsLibTwoTargetPreference", - "SettingsLibSettingsTransition", - "SettingsLibButtonPreference", - "SettingsLibDeviceStateRotationLock", - "SettingsLibProfileSelector", + "SettingsLibSearchWidget", + "SettingsLibUtils", + "SettingsLibWidget", "setupdesign", "zxing-core-1.7", "androidx.room_room-runtime", "settingslib_flags_lib", - ], plugins: ["androidx.room_room-compiler-plugin"], - + use_resource_processor: true, resource_dirs: ["res"], srcs: [ @@ -72,6 +47,43 @@ android_library { ], } +// Group all the libraries with namespace "com.android.settingslib.widget", to allow SettingsLib to +// set use_resource_processor = true. +// We can remove SettingsLibWidget when all these libraries have its own namespace. +android_library { + name: "SettingsLibWidget", + visibility: ["//visibility:private"], + manifest: "AndroidManifest-SettingsLibWidget.xml", + static_libs: [ + "SettingsLibActionBarShadow", + "SettingsLibActionButtonsPreference", + "SettingsLibAdaptiveIcon", + "SettingsLibAppPreference", + "SettingsLibBannerMessagePreference", + "SettingsLibBarChartPreference", + "SettingsLibButtonPreference", + "SettingsLibCollapsingToolbarBaseActivity", + "SettingsLibEntityHeaderWidgets", + "SettingsLibFooterPreference", + "SettingsLibHelpUtils", + "SettingsLibIllustrationPreference", + "SettingsLibLayoutPreference", + "SettingsLibMainSwitchPreference", + "SettingsLibProfileSelector", + "SettingsLibProgressBar", + "SettingsLibRadioButtonPreference", + "SettingsLibRestrictedLockUtils", + "SettingsLibSelectorWithWidgetPreference", + "SettingsLibSettingsSpinner", + "SettingsLibSettingsTransition", + "SettingsLibTopIntroPreference", + "SettingsLibTwoTargetPreference", + "SettingsLibUsageProgressBarPreference", + ], + + resource_dirs: [], +} + // NOTE: Keep this module in sync with ./common.mk java_defaults { name: "SettingsLibDefaults", diff --git a/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml b/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml new file mode 100644 index 000000000000..38a7d6ace1f8 --- /dev/null +++ b/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest package="com.android.settingslib.widget" /> diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt index 90a723f707da..b77368a429ae 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt @@ -253,6 +253,7 @@ private fun SingleRowTopAppBar( hideTitleSemantics = false, navigationIcon = navigationIcon, actions = actionsRow, + titleScaleDisabled = false, ) } } @@ -426,6 +427,7 @@ private fun TwoRowsTopAppBar( * accessibility services at the same time, when animating between collapsed / expanded states. * @param navigationIcon a navigation icon [Composable] * @param actions actions [Composable] + * @param titleScaleDisabled whether the title font scaling is disabled. Default is disabled. */ @Composable private fun TopAppBarLayout( @@ -443,6 +445,7 @@ private fun TopAppBarLayout( hideTitleSemantics: Boolean, navigationIcon: @Composable () -> Unit, actions: @Composable () -> Unit, + titleScaleDisabled: Boolean = true, ) { Layout( { @@ -466,9 +469,12 @@ private fun TopAppBarLayout( ProvideTextStyle(value = titleTextStyle) { CompositionLocalProvider( LocalContentColor provides titleContentColor, - // Disable the title font scaling by only passing the density but not the - // font scale. - LocalDensity provides Density(density = LocalDensity.current.density), + LocalDensity provides with(LocalDensity.current) { + Density( + density = density, + fontScale = if (titleScaleDisabled) 1f else fontScale, + ) + }, content = title ) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt index d437e3579354..696e8776ccd7 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt @@ -18,6 +18,7 @@ package com.android.settingslib.spa.widget.scaffold import androidx.activity.compose.BackHandler import androidx.appcompat.R +import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.RowScope @@ -96,7 +97,8 @@ fun SearchScaffold( Modifier .padding(paddingValues.horizontalValues()) .padding(top = paddingValues.calculateTopPadding()) - .fillMaxSize(), + .focusable() + .fillMaxSize() ) { content( paddingValues.calculateBottomPadding(), diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index af06d7304160..b1d1ea5eda42 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -126,10 +126,10 @@ public class RestrictedSwitchPreference extends SwitchPreference { switchSummary = isChecked() ? getUpdatableEnterpriseString( getContext(), ENABLED_BY_ADMIN_SWITCH_SUMMARY, - R.string.enabled_by_admin) + com.android.settingslib.widget.R.string.enabled_by_admin) : getUpdatableEnterpriseString( getContext(), DISABLED_BY_ADMIN_SWITCH_SUMMARY, - R.string.disabled_by_admin); + com.android.settingslib.widget.R.string.disabled_by_admin); } else { switchSummary = mRestrictedSwitchSummary; } diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 6eb2f3834858..96bb4b586dfc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -1776,7 +1776,8 @@ public class ApplicationsState { final int userId = UserHandle.getUserId(this.info.uid); if (UserManager.get(context).isManagedProfile(userId)) { this.labelDescription = context.getString( - com.android.settingslib.R.string.accessibility_work_profile_app_description, + com.android.settingslib.utils.R + .string.accessibility_work_profile_app_description, this.label); } else { this.labelDescription = this.label; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java index cb4eba422366..f5257b070392 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java @@ -46,7 +46,8 @@ public class BroadcastDialog extends AlertDialog { View layout = View.inflate(mContext, R.layout.broadcast_dialog, null); final Window window = getWindow(); window.setContentView(layout); - window.setWindowAnimations(R.style.Theme_AlertDialog_SettingsLib); + window.setWindowAnimations( + com.android.settingslib.widget.R.style.Theme_AlertDialog_SettingsLib); TextView title = layout.findViewById(R.id.dialog_title); TextView subTitle = layout.findViewById(R.id.dialog_subtitle); diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java index 1f4cafce835f..d53c3a7aafa6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java @@ -153,7 +153,7 @@ public class InputMethodPreference extends PrimarySwitchPreference } final ImageView icon = holder.itemView.findViewById(android.R.id.icon); final int iconSize = getContext().getResources().getDimensionPixelSize( - R.dimen.secondary_app_icon_size); + com.android.settingslib.widget.R.dimen.secondary_app_icon_size); if (icon != null && iconSize > 0) { ViewGroup.LayoutParams params = icon.getLayoutParams(); params.height = iconSize; diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java index ebdfbeade99d..251cd3615897 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java @@ -32,7 +32,6 @@ import android.util.IconDrawableFactory; import android.util.Log; import com.android.launcher3.icons.BaseIconFactory; -import com.android.settingslib.R; import com.android.settingslib.Utils; /** @@ -81,7 +80,7 @@ public class ConversationIconFactory extends BaseIconFactory { mPackageManager = pm; mIconDrawableFactory = iconDrawableFactory; mImportantConversationColor = context.getResources().getColor( - R.color.important_conversation, null); + com.android.launcher3.icons.R.color.important_conversation, null); } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java index 21eb35abe32c..2d6f0587fbf7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java @@ -167,7 +167,8 @@ public class AccessPointPreference extends Preference { ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon); bindFrictionImage(frictionImageView); - final View divider = view.findViewById(R.id.two_target_divider); + final View divider = + view.findViewById(com.android.settingslib.widget.R.id.two_target_divider); divider.setVisibility(shouldShowDivider() ? View.VISIBLE : View.INVISIBLE); } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index c45d77471932..015356e013b7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -331,7 +331,8 @@ public class WifiStatusTracker { return; } else if (!isDefaultNetwork && mDefaultNetworkCapabilities != null && mDefaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { - statusLabel = mContext.getString(R.string.wifi_connected_low_quality); + statusLabel = mContext.getString( + com.android.wifitrackerlib.R.string.wifi_connected_low_quality); return; } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java index 74c2fc8cce4c..32a16716d9f9 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java @@ -54,7 +54,7 @@ public class PrimarySwitchPreferenceTest { mPreference = new PrimarySwitchPreference(mContext); LayoutInflater inflater = LayoutInflater.from(mContext); mHolder = PreferenceViewHolder.createInstanceForTests(inflater.inflate( - com.android.settingslib.R.layout.preference_two_target, null)); + com.android.settingslib.widget.R.layout.preference_two_target, null)); mWidgetView = mHolder.itemView.findViewById(android.R.id.widget_frame); inflater.inflate(R.layout.preference_widget_primary_switch, mWidgetView, true); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java index 06343f59e2b8..ff84dc92037f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java @@ -55,7 +55,8 @@ public class CollapsingCoordinatorLayoutTest { @Test public void onCreate_userAddedChildViewsBeMovedToContentFrame() { CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout(); - View contentFrameView = layout.findViewById(R.id.content_frame); + View contentFrameView = + layout.findViewById(com.android.settingslib.widget.R.id.content_frame); TextView textView = contentFrameView.findViewById(R.id.text_hello_world); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java index 471dac05e6b4..8246aff8b18e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java @@ -29,8 +29,6 @@ import android.view.WindowManager; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import com.android.settingslib.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -95,7 +93,7 @@ public class HideNonSystemOverlayMixinTest { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setTheme(R.style.Theme_AppCompat); + setTheme(androidx.appcompat.R.style.Theme_AppCompat); getLifecycle().addObserver(new HideNonSystemOverlayMixin(this)); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java index 7b08fee9ac91..2f8967e34e79 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java @@ -30,8 +30,6 @@ import android.content.Context; import androidx.test.core.app.ApplicationProvider; -import com.android.settingslib.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -51,7 +49,7 @@ public class FinancedDeviceActionDisabledByAdminControllerTest { @Before public void setUp() { - mActivity.setTheme(R.style.Theme_AppCompat_DayNight); + mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight); mController.initialize(mTestUtils.createLearnMoreButtonLauncher()); mController.updateEnforcedAdmin(ENFORCED_ADMIN, ENFORCEMENT_ADMIN_USER_ID); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java index 7b885660ccea..f168122994c3 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java @@ -33,8 +33,6 @@ import android.os.UserManager; import androidx.test.core.app.ApplicationProvider; -import com.android.settingslib.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -57,7 +55,7 @@ public class ManagedDeviceActionDisabledByAdminControllerTest { @Before public void setUp() { - mActivity.setTheme(R.style.Theme_AppCompat_DayNight); + mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java index 66a2ea69559b..68312223b4b1 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java @@ -62,7 +62,7 @@ public class CreateUserDialogControllerTest { public void setup() { MockitoAnnotations.initMocks(this); mActivity = spy(ActivityController.of(new FragmentActivity()).get()); - mActivity.setTheme(R.style.Theme_AppCompat_DayNight); + mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight); mUnderTest = new TestCreateUserDialogController(); mPhotoRestrictedByBase = false; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java index f595cd334105..a95257a903e0 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java @@ -99,7 +99,7 @@ public class EditUserInfoControllerTest { public void setup() { MockitoAnnotations.initMocks(this); mActivity = spy(ActivityController.of(new FragmentActivity()).get()); - mActivity.setTheme(R.style.Theme_AppCompat_DayNight); + mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight); mController = new TestEditUserInfoController(); mPhotoRestrictedByBase = false; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java index 6cbae05ce9a5..10862403ae70 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java @@ -105,15 +105,15 @@ public class AdaptiveIconTest { icon.setBackgroundColor(mContext, tile); - assertThat(icon.mBackgroundColor) - .isEqualTo(mContext.getColor(R.color.homepage_generic_icon_background)); + assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor( + com.android.settingslib.widget.R.color.homepage_generic_icon_background)); } @Test public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() { final Tile tile = spy(new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, - R.color.bt_outline_color); + com.android.settingslib.widget.R.color.bt_outline_color); doReturn(Icon.createWithResource(mContext, R.drawable.ic_system_update)) .when(tile).getIcon(mContext); @@ -121,8 +121,8 @@ public class AdaptiveIconTest { new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); icon.setBackgroundColor(mContext, tile); - assertThat(icon.mBackgroundColor) - .isEqualTo(mContext.getColor(R.color.bt_outline_color)); + assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor( + com.android.settingslib.widget.R.color.bt_outline_color)); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java index 71d55bc3de2a..b2bc53dda1fa 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java @@ -21,8 +21,6 @@ import static com.google.common.truth.Truth.assertThat; import android.content.res.Resources; import android.graphics.Paint; -import com.android.settingslib.R; - import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java index 049c90e971a9..a26f200ec603 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java @@ -58,14 +58,15 @@ public class FooterPreferenceTest { @Test public void setLearnMoreText_shouldSetAsTextInLearnMore() { final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( - LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)); + LayoutInflater.from(mContext) + .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)); mFooterPreference.setLearnMoreText("Custom learn more"); mFooterPreference.setLearnMoreAction(view -> { /* do nothing */ } /* listener */); mFooterPreference.onBindViewHolder(holder); assertThat(((TextView) holder.findViewById( - R.id.settingslib_learn_more)).getText().toString()) + com.android.settingslib.widget.R.id.settingslib_learn_more)).getText().toString()) .isEqualTo("Custom learn more"); } @@ -94,8 +95,9 @@ public class FooterPreferenceTest { @Test public void onBindViewHolder_whenTitleIsNull_shouldNotRaiseNpe() { PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests( - LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null))); - when(viewHolder.findViewById(R.id.title)).thenReturn(null); + LayoutInflater.from(mContext) + .inflate(com.android.settingslib.widget.R.layout.preference_footer, null))); + when(viewHolder.findViewById(androidx.core.R.id.title)).thenReturn(null); Throwable actualThrowable = null; try { @@ -110,8 +112,10 @@ public class FooterPreferenceTest { @Test public void onBindViewHolder_whenLearnMoreIsNull_shouldNotRaiseNpe() { PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests( - LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null))); - when(viewHolder.findViewById(R.id.settingslib_learn_more)).thenReturn(null); + LayoutInflater.from(mContext) + .inflate(com.android.settingslib.widget.R.layout.preference_footer, null))); + when(viewHolder.findViewById(com.android.settingslib.widget.R.id.settingslib_learn_more)) + .thenReturn(null); Throwable actualThrowable = null; try { @@ -126,7 +130,8 @@ public class FooterPreferenceTest { @Test public void onBindViewHolder_whenIconFrameIsNull_shouldNotRaiseNpe() { PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests( - LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null))); + LayoutInflater.from(mContext) + .inflate(com.android.settingslib.widget.R.layout.preference_footer, null))); when(viewHolder.findViewById(R.id.icon_frame)).thenReturn(null); Throwable actualThrowable = null; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java index aaec909aa335..23b4c2aec80f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java @@ -32,8 +32,6 @@ import android.widget.LinearLayout; import androidx.preference.PreferenceViewHolder; -import com.android.settingslib.R; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index 0d7d9cc9c9e5..017ac6019b65 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -446,7 +446,9 @@ private fun LottieAnimationView.addOverlayDynamicColor( for (key in listOf(".blue600", ".blue400")) { addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) { PorterDuffColorFilter( - context.getColor(com.android.settingslib.R.color.settingslib_color_blue400), + context.getColor( + com.android.settingslib.color.R.color.settingslib_color_blue400 + ), PorterDuff.Mode.SRC_ATOP ) } diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt index f68078a8a340..82b032450938 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt @@ -29,6 +29,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.Compile import com.android.systemui.util.traceSection import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -239,7 +240,7 @@ constructor( private companion object { const val TAG = "DisplayRepository" - val DEBUG = Log.isLoggable(TAG, Log.DEBUG) + val DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index d8b31a229261..d8bc5ff75b61 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -636,7 +636,13 @@ object Flags { val CLIPBOARD_SHARED_TRANSITIONS = unreleasedFlag("clipboard_shared_transitions", teamfood = true) + /** + * Whether the scene container (Flexiglass) is enabled. Note that [SCENE_CONTAINER] should be + * checked and toggled together with [SCENE_CONTAINER_ENABLED] so that ProGuard can remove + * unused code from our APK at compile time. + */ // TODO(b/283300105): Tracking Bug + @JvmField val SCENE_CONTAINER_ENABLED = false @JvmField val SCENE_CONTAINER = unreleasedFlag("scene_container") // 1900 @@ -764,11 +770,11 @@ object Flags { /** Enable the Compose implementation of the PeopleSpaceActivity. */ @JvmField - val COMPOSE_PEOPLE_SPACE = unreleasedFlag("compose_people_space") + val COMPOSE_PEOPLE_SPACE = releasedFlag("compose_people_space") /** Enable the Compose implementation of the Quick Settings footer actions. */ @JvmField - val COMPOSE_QS_FOOTER_ACTIONS = unreleasedFlag("compose_qs_footer_actions") + val COMPOSE_QS_FOOTER_ACTIONS = releasedFlag("compose_qs_footer_actions") /** Enable the share wifi button in Quick Settings internet dialog. */ @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 8db7abf7347f..c1999043743c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -163,6 +163,7 @@ import com.android.systemui.plugins.FalsingManager.FalsingTapListener; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; +import com.android.systemui.shade.data.repository.ShadeRepository; import com.android.systemui.shade.transition.ShadeTransitionController; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.systemui.shared.system.QuickStepContract; @@ -350,6 +351,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final Interpolator mBounceInterpolator; private final NotificationShadeWindowController mNotificationShadeWindowController; private final ShadeExpansionStateManager mShadeExpansionStateManager; + private final ShadeRepository mShadeRepository; private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired; private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate(); private final NotificationGutsManager mGutsManager; @@ -710,7 +712,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump VibratorHelper vibratorHelper, LatencyTracker latencyTracker, PowerManager powerManager, - AccessibilityManager accessibilityManager, @DisplayId int displayId, + AccessibilityManager accessibilityManager, + @DisplayId int displayId, KeyguardUpdateMonitor keyguardUpdateMonitor, MetricsLogger metricsLogger, ShadeLogger shadeLogger, @@ -746,6 +749,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ScreenOffAnimationController screenOffAnimationController, LockscreenGestureLogger lockscreenGestureLogger, ShadeExpansionStateManager shadeExpansionStateManager, + ShadeRepository shadeRepository, Optional<SysUIUnfoldComponent> unfoldComponent, SysUiState sysUiState, Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider, @@ -788,6 +792,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mLockscreenGestureLogger = lockscreenGestureLogger; mShadeExpansionStateManager = shadeExpansionStateManager; + mShadeRepository = shadeRepository; mShadeLog = shadeLogger; mGutsManager = gutsManager; mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel; @@ -3952,6 +3957,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } mExpandedFraction = Math.min(1f, maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight); + mShadeRepository.setLegacyShadeExpansion(mExpandedFraction); mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction); mExpansionDragDownAmountPx = h; mAmbientState.setExpansionFraction(mExpandedFraction); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index 6585fcb1ae53..ed719a651978 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -77,7 +77,9 @@ abstract class ShadeViewProviderModule { layoutInsetController: NotificationInsetsController, ): WindowRootView { return if ( - featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable() + Flags.SCENE_CONTAINER_ENABLED && + featureFlags.isEnabled(Flags.SCENE_CONTAINER) && + ComposeFacade.isComposeAvailable() ) { val sceneWindowRootView = layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt index 5a8be1e328f6..509921fd38e1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt @@ -34,21 +34,48 @@ interface ShadeRepository { /** ShadeModel information regarding shade expansion events */ val shadeModel: Flow<ShadeModel> - /** Amount qs has expanded. Quick Settings can be expanded without the full shade expansion. */ + /** + * Amount qs has expanded, [0-1]. 0 means fully collapsed, 1 means fully expanded. Quick + * Settings can be expanded without the full shade expansion. + */ val qsExpansion: StateFlow<Float> - /** The amount the shade has expanded */ - val shadeExpansion: StateFlow<Float> + /** + * The amount the lockscreen shade has dragged down by the user, [0-1]. 0 means fully collapsed, + * 1 means fully expanded. + */ + val lockscreenShadeExpansion: StateFlow<Float> + + /** + * NotificationPanelViewController.mExpandedFraction as a StateFlow. This nominally represents + * the amount the shade has expanded 0-1 like many other flows in this repo, but there are cases + * where its value will be 1 and no shade will be rendered, e.g. whenever the keyguard is + * visible and when quick settings is expanded. The confusing nature and impending deletion of + * this makes it unsuitable for future development, so usage is discouraged. + */ + @Deprecated("Use ShadeInteractor.shadeExpansion instead") + val legacyShadeExpansion: StateFlow<Float> /** Amount shade has expanded with regard to the UDFPS location */ val udfpsTransitionToFullShadeProgress: StateFlow<Float> /** The amount QS has expanded without notifications */ fun setQsExpansion(qsExpansion: Float) + fun setUdfpsTransitionToFullShadeProgress(progress: Float) - /** The amount the shade has expanded, [0-1]. 0 means fully collapsed, 1 means fully expanded */ - fun setShadeExpansion(expansion: Float) + /** + * Set the amount the shade has dragged down by the user, [0-1]. 0 means fully collapsed, 1 + * means fully expanded. + */ + fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) + + /** + * Set the legacy expansion value. This should only be called whenever the value of + * NotificationPanelViewController.mExpandedFraction changes or in tests. + */ + @Deprecated("Should only be called by NPVC and tests") + fun setLegacyShadeExpansion(expandedFraction: Float) } /** Business logic for shade interactions */ @@ -84,18 +111,29 @@ constructor(shadeExpansionStateManager: ShadeExpansionStateManager) : ShadeRepos private val _qsExpansion = MutableStateFlow(0f) override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow() - private val _shadeExpansion = MutableStateFlow(0f) - override val shadeExpansion: StateFlow<Float> = _shadeExpansion.asStateFlow() + private val _lockscreenShadeExpansion = MutableStateFlow(0f) + override val lockscreenShadeExpansion: StateFlow<Float> = + _lockscreenShadeExpansion.asStateFlow() private var _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f) override val udfpsTransitionToFullShadeProgress: StateFlow<Float> = _udfpsTransitionToFullShadeProgress.asStateFlow() + + private val _legacyShadeExpansion = MutableStateFlow(0f) + @Deprecated("Use ShadeInteractor.shadeExpansion instead") + override val legacyShadeExpansion: StateFlow<Float> = _legacyShadeExpansion.asStateFlow() + override fun setQsExpansion(qsExpansion: Float) { _qsExpansion.value = qsExpansion } - override fun setShadeExpansion(expansion: Float) { - _shadeExpansion.value = expansion + @Deprecated("Should only be called by NPVC and tests") + override fun setLegacyShadeExpansion(expandedFraction: Float) { + _legacyShadeExpansion.value = expandedFraction + } + + override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) { + _lockscreenShadeExpansion.value = lockscreenShadeExpansion } override fun setUdfpsTransitionToFullShadeProgress(progress: Float) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index 288d32e58c7c..fd63b89d1199 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -22,6 +22,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.user.domain.interactor.UserInteractor @@ -31,6 +32,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -45,6 +47,7 @@ constructor( userSetupRepository: UserSetupRepository, deviceProvisionedController: DeviceProvisionedController, userInteractor: UserInteractor, + sharedNotificationContainerInteractor: SharedNotificationContainerInteractor, repository: ShadeRepository, ) { /** Emits true if the shade is currently allowed and false otherwise. */ @@ -53,16 +56,31 @@ constructor( .map { it.isShadeEnabled() } .stateIn(scope, SharingStarted.Eagerly, initialValue = false) + /** + * Whether split shade, the combined notifications and quick settings shade used for large + * screens, is enabled. + */ + val splitShadeEnabled: Flow<Boolean> = + sharedNotificationContainerInteractor.configurationBasedDimensions + .map { dimens -> dimens.useSplitShade } + .distinctUntilChanged() + /** The amount [0-1] that the shade has been opened */ val shadeExpansion: Flow<Float> = - combine(repository.shadeExpansion, keyguardRepository.statusBarState) { - shadeExpansion, - statusBarState -> - // This is required, as shadeExpansion gets reset to 0f even with the shade open - if (statusBarState == StatusBarState.SHADE_LOCKED) { - 1f - } else { - shadeExpansion + combine( + repository.lockscreenShadeExpansion, + keyguardRepository.statusBarState, + repository.legacyShadeExpansion, + repository.qsExpansion, + splitShadeEnabled + ) { dragDownAmount, statusBarState, legacyShadeExpansion, qsExpansion, splitShadeEnabled -> + when (statusBarState) { + // legacyShadeExpansion is 1 instead of 0 when QS is expanded + StatusBarState.SHADE -> + if (!splitShadeEnabled && qsExpansion > 0f) 0f else legacyShadeExpansion + StatusBarState.KEYGUARD -> dragDownAmount + // This is required, as shadeExpansion gets reset to 0f even with the shade open + StatusBarState.SHADE_LOCKED -> 1f } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index f004982413e8..73bbbca09fe8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -447,7 +447,7 @@ class LockscreenShadeTransitionController @Inject constructor( if (!nsslController.isInLockedDownShade() || field == 0f || forceApplyAmount) { fractionToShade = MathUtils.saturate(dragDownAmount / notificationShelfTransitionDistance) - shadeRepository.setShadeExpansion(fractionToShade) + shadeRepository.setLockscreenShadeExpansion(fractionToShade) nsslController.setTransitionToFullShadeAmount(fractionToShade) qsTransitionController.dragDownAmount = value @@ -857,12 +857,12 @@ class DragDownHelper( MotionEvent.ACTION_MOVE -> { val h = y - initialTouchY // Adjust the touch slop if another gesture may be being performed. - val touchSlop = if (event.classification - == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE) { - touchSlop * slopMultiplier - } else { - touchSlop - } + val touchSlop = + if (event.classification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE) { + touchSlop * slopMultiplier + } else { + touchSlop + } if (h > touchSlop && h > Math.abs(x - initialTouchX)) { isDraggingDown = true captureStartingChild(initialTouchX, initialTouchY) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index c573ac638032..1c9ec27c6a1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -626,6 +626,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mScreenOffAnimationController, mLockscreenGestureLogger, mShadeExpansionStateManager, + mShadeRepository, mSysUIUnfoldComponent, mSysUiState, () -> mKeyguardBottomAreaViewController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index ab0ae05b0e83..e42a7a6f50d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -37,6 +37,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.TestScopeProvider; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; @@ -61,6 +62,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor; import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; @@ -174,6 +176,9 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { new FakeUserSetupRepository(), mDeviceProvisionedController, mUserInteractor, + new SharedNotificationContainerInteractor( + new FakeConfigurationRepository(), + mContext), mShadeRepository ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index e6e7482bdaf9..41ea5b747e06 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt @@ -25,7 +25,9 @@ import android.os.UserManager import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags @@ -36,6 +38,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository @@ -52,6 +55,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -71,6 +75,12 @@ class ShadeInteractorTest : SysuiTestCase() { private val disableFlagsRepository = FakeDisableFlagsRepository() private val keyguardRepository = FakeKeyguardRepository() private val shadeRepository = FakeShadeRepository() + private val configurationRepository = FakeConfigurationRepository() + private val sharedNotificationContainerInteractor = + SharedNotificationContainerInteractor( + configurationRepository, + mContext, + ) @Mock private lateinit var manager: UserManager @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode @@ -145,6 +155,7 @@ class ShadeInteractorTest : SysuiTestCase() { userSetupRepository, deviceProvisionedController, userInteractor, + sharedNotificationContainerInteractor, shadeRepository, ) } @@ -363,7 +374,7 @@ class ShadeInteractorTest : SysuiTestCase() { val actual by collectLastValue(underTest.shadeExpansion) keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) - shadeRepository.setShadeExpansion(0.5f) + shadeRepository.setLockscreenShadeExpansion(0.5f) assertThat(actual).isEqualTo(1f) } @@ -375,10 +386,52 @@ class ShadeInteractorTest : SysuiTestCase() { keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - shadeRepository.setShadeExpansion(0.5f) + shadeRepository.setLockscreenShadeExpansion(0.5f) assertThat(actual).isEqualTo(0.5f) - shadeRepository.setShadeExpansion(0.8f) + shadeRepository.setLockscreenShadeExpansion(0.8f) assertThat(actual).isEqualTo(0.8f) } + + fun shadeExpansionWhenInSplitShadeAndQsExpanded() = + testScope.runTest { + val actual by collectLastValue(underTest.shadeExpansion) + + // WHEN split shade is enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + overrideResource(R.bool.config_use_split_notification_shade, true) + configurationRepository.onAnyConfigurationChange() + runCurrent() + shadeRepository.setQsExpansion(.5f) + shadeRepository.setLegacyShadeExpansion(.7f) + + // THEN legacy shade expansion is passed through + assertThat(actual).isEqualTo(.7f) + } + + fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() = + testScope.runTest { + val actual by collectLastValue(underTest.shadeExpansion) + + // WHEN split shade is not enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + shadeRepository.setQsExpansion(.5f) + shadeRepository.setLegacyShadeExpansion(1f) + + // THEN shade expansion is zero + assertThat(actual).isEqualTo(0f) + } + + fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() = + testScope.runTest { + val actual by collectLastValue(underTest.shadeExpansion) + + // WHEN split shade is not enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLegacyShadeExpansion(.6f) + + // THEN shade expansion is zero + assertThat(actual).isEqualTo(.6f) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt index fdaea2220de9..e086712308b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt @@ -121,18 +121,33 @@ class ShadeRepositoryImplTest : SysuiTestCase() { } @Test - fun updateShadeExpansion() = + fun updateDragDownAmount() = testScope.runTest { - assertThat(underTest.shadeExpansion.value).isEqualTo(0f) + assertThat(underTest.lockscreenShadeExpansion.value).isEqualTo(0f) - underTest.setShadeExpansion(.5f) - assertThat(underTest.shadeExpansion.value).isEqualTo(.5f) + underTest.setLockscreenShadeExpansion(.5f) + assertThat(underTest.lockscreenShadeExpansion.value).isEqualTo(.5f) - underTest.setShadeExpansion(.82f) - assertThat(underTest.shadeExpansion.value).isEqualTo(.82f) + underTest.setLockscreenShadeExpansion(.82f) + assertThat(underTest.lockscreenShadeExpansion.value).isEqualTo(.82f) - underTest.setShadeExpansion(1f) - assertThat(underTest.shadeExpansion.value).isEqualTo(1f) + underTest.setLockscreenShadeExpansion(1f) + assertThat(underTest.lockscreenShadeExpansion.value).isEqualTo(1f) + } + + @Test + fun updateLegacyShadeExpansion() = + testScope.runTest { + assertThat(underTest.legacyShadeExpansion.value).isEqualTo(0f) + + underTest.setLegacyShadeExpansion(.5f) + assertThat(underTest.legacyShadeExpansion.value).isEqualTo(.5f) + + underTest.setLegacyShadeExpansion(.82f) + assertThat(underTest.legacyShadeExpansion.value).isEqualTo(.82f) + + underTest.setLegacyShadeExpansion(1f) + assertThat(underTest.legacyShadeExpansion.value).isEqualTo(1f) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 51e72c615372..6f7588097c70 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -10,6 +10,7 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -29,6 +30,7 @@ import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.LSShadeTransitionLogger @@ -102,6 +104,11 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback private val disableFlagsRepository = FakeDisableFlagsRepository() private val keyguardRepository = FakeKeyguardRepository() + private val configurationRepository = FakeConfigurationRepository() + private val sharedNotificationContainerInteractor = SharedNotificationContainerInteractor( + configurationRepository, + mContext, + ) private val shadeInteractor = ShadeInteractor( testScope.backgroundScope, @@ -110,6 +117,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { userSetupRepository = FakeUserSetupRepository(), deviceProvisionedController = mock(), userInteractor = mock(), + sharedNotificationContainerInteractor, repository = FakeShadeRepository(), ) private val powerInteractor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 55b52dc21434..75fb22dfddd0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.shade.data.repository.FakeShadeRepository @@ -97,7 +98,11 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { keyguardTransitionInteractor = it.keyguardTransitionInteractor keyguardTransitionRepository = it.repository } - + sharedNotificationContainerInteractor = + SharedNotificationContainerInteractor( + configurationRepository, + mContext, + ) shadeInteractor = ShadeInteractor( testScope.backgroundScope, @@ -106,14 +111,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { userSetupRepository, deviceProvisionedController, userInteractor, + sharedNotificationContainerInteractor, shadeRepository, ) - - sharedNotificationContainerInteractor = - SharedNotificationContainerInteractor( - configurationRepository, - mContext, - ) underTest = SharedNotificationContainerViewModel( sharedNotificationContainerInteractor, @@ -228,7 +228,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { val isOnLockscreenWithoutShade by collectLastValue(underTest.isOnLockscreenWithoutShade) // First on AOD - shadeRepository.setShadeExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) shadeRepository.setQsExpansion(0f) keyguardTransitionRepository.sendTransitionStep( TransitionStep( @@ -242,19 +242,19 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { showLockscreen() // While state is LOCKSCREEN, validate variations of both shade and qs expansion - shadeRepository.setShadeExpansion(0.1f) + shadeRepository.setLockscreenShadeExpansion(0.1f) shadeRepository.setQsExpansion(0f) assertThat(isOnLockscreenWithoutShade).isFalse() - shadeRepository.setShadeExpansion(0.1f) + shadeRepository.setLockscreenShadeExpansion(0.1f) shadeRepository.setQsExpansion(0.1f) assertThat(isOnLockscreenWithoutShade).isFalse() - shadeRepository.setShadeExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) shadeRepository.setQsExpansion(0.1f) assertThat(isOnLockscreenWithoutShade).isFalse() - shadeRepository.setShadeExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) shadeRepository.setQsExpansion(0f) assertThat(isOnLockscreenWithoutShade).isTrue() } @@ -366,8 +366,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } private suspend fun showLockscreen() { - shadeRepository.setShadeExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) shadeRepository.setQsExpansion(0f) + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) keyguardTransitionRepository.sendTransitionStep( TransitionStep( to = KeyguardState.LOCKSCREEN, @@ -377,8 +378,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } private suspend fun showLockscreenWithShadeExpanded() { - shadeRepository.setShadeExpansion(1f) + shadeRepository.setLockscreenShadeExpansion(1f) shadeRepository.setQsExpansion(0f) + keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) keyguardTransitionRepository.sendTransitionStep( TransitionStep( to = KeyguardState.LOCKSCREEN, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt index ccddca2d6611..08152a3c49cd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt @@ -33,8 +33,12 @@ class FakeShadeRepository : ShadeRepository { private val _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f) override val udfpsTransitionToFullShadeProgress = _udfpsTransitionToFullShadeProgress - private val _shadeExpansion = MutableStateFlow(0f) - override val shadeExpansion = _shadeExpansion + private val _lockscreenShadeExpansion = MutableStateFlow(0f) + override val lockscreenShadeExpansion = _lockscreenShadeExpansion + + private val _legacyShadeExpansion = MutableStateFlow(0f) + @Deprecated("Use ShadeInteractor instead") + override val legacyShadeExpansion = _legacyShadeExpansion fun setShadeModel(model: ShadeModel) { _shadeModel.value = model @@ -48,7 +52,12 @@ class FakeShadeRepository : ShadeRepository { _udfpsTransitionToFullShadeProgress.value = progress } - override fun setShadeExpansion(expansion: Float) { - _shadeExpansion.value = expansion + override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) { + _lockscreenShadeExpansion.value = lockscreenShadeExpansion + } + + @Deprecated("Should only be called by NPVC and tests") + override fun setLegacyShadeExpansion(expandedFraction: Float) { + _legacyShadeExpansion.value = expandedFraction } } diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index cf6092eda92f..9e7b897119a1 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -136,7 +136,8 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController @Nullable private final SecureWindowCallback mSecureWindowCallback; @Nullable private final Set<String> mDisplayCategories; - private final boolean mShowTasksInHostDeviceRecents; + @GuardedBy("mGenericWindowPolicyControllerLock") + private boolean mShowTasksInHostDeviceRecents; /** * Creates a window policy controller that is generic to the different use cases of virtual @@ -204,6 +205,15 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController mDisplayId = displayId; } + /** + * Set whether to show activities in recents on the host device. + */ + public void setShowInHostDeviceRecents(boolean showInHostDeviceRecents) { + synchronized (mGenericWindowPolicyControllerLock) { + mShowTasksInHostDeviceRecents = showInHostDeviceRecents; + } + } + /** Register a listener for running applications changes. */ public void registerRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) { synchronized (mGenericWindowPolicyControllerLock) { @@ -347,7 +357,9 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController @Override public boolean canShowTasksInHostDeviceRecents() { - return mShowTasksInHostDeviceRecents; + synchronized (mGenericWindowPolicyControllerLock) { + return mShowTasksInHostDeviceRecents; + } } @Override diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index b8b42fba71ab..56afeb112068 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -19,6 +19,8 @@ package com.android.server.companion.virtual; import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED; import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY; import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY; +import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; +import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; @@ -42,6 +44,7 @@ import android.companion.virtual.VirtualDeviceManager.ActivityListener; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; +import android.companion.virtual.flags.Flags; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorEvent; import android.content.AttributionSource; @@ -82,6 +85,7 @@ import android.util.ArraySet; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseIntArray; import android.view.Display; import android.view.WindowManager; import android.widget.Toast; @@ -130,8 +134,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final int mOwnerUid; private final VirtualDeviceLog mVirtualDeviceLog; private final String mOwnerPackageName; - private int mDeviceId; - private @Nullable String mPersistentDeviceId; + private final int mDeviceId; + private @Nullable final String mPersistentDeviceId; // Thou shall not hold the mVirtualDeviceLock over the mInputController calls. // Holding the lock can lead to lock inversion with GlobalWindowManagerLock. // 1. After display is created the window manager calls into VDM during construction @@ -147,6 +151,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final IBinder mAppToken; private final VirtualDeviceParams mParams; @GuardedBy("mVirtualDeviceLock") + private final SparseIntArray mDevicePolicies; + @GuardedBy("mVirtualDeviceLock") private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>(); private final IVirtualDeviceActivityListener mActivityListener; private final IVirtualDeviceSoundEffectListener mSoundEffectListener; @@ -154,7 +160,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @GuardedBy("mVirtualDeviceLock") private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>(); @NonNull - private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback; + private final Consumer<ArraySet<Integer>> mRunningAppsChangedCallback; // The default setting for showing the pointer on new displays. @GuardedBy("mVirtualDeviceLock") private boolean mDefaultShowPointerIcon = true; @@ -261,6 +267,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mDeviceId = deviceId; mAppToken = token; mParams = params; + mDevicePolicies = params.getDevicePolicies(); mDisplayManager = displayManager; if (inputController == null) { mInputController = new InputController( @@ -323,7 +330,13 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub /** Returns the policy specified for this policy type */ public @VirtualDeviceParams.DevicePolicy int getDevicePolicy( @VirtualDeviceParams.PolicyType int policyType) { - return mParams.getDevicePolicy(policyType); + if (Flags.dynamicPolicy()) { + synchronized (mVirtualDeviceLock) { + return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT); + } + } else { + return mParams.getDevicePolicy(policyType); + } } /** Returns device-specific audio session id for playback. */ @@ -412,15 +425,13 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub public void close() { super.close_enforcePermission(); // Remove about-to-be-closed virtual device from the service before butchering it. - boolean removed = mService.removeVirtualDevice(mDeviceId); - mVirtualDeviceLog.logClosed(mDeviceId, mOwnerUid); - mDeviceId = Context.DEVICE_ID_INVALID; - - // Device is already closed. - if (!removed) { + if (!mService.removeVirtualDevice(mDeviceId)) { + // Device is already closed. return; } + mVirtualDeviceLog.logClosed(mDeviceId, mOwnerUid); + final long ident = Binder.clearCallingIdentity(); try { VirtualDisplayWrapper[] virtualDisplaysToBeReleased; @@ -510,6 +521,27 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @Override // Binder call @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType, + @VirtualDeviceParams.DevicePolicy int devicePolicy) { + super.setDevicePolicy_enforcePermission(); + switch (policyType) { + case POLICY_TYPE_RECENTS: + synchronized (mVirtualDeviceLock) { + mDevicePolicies.put(policyType, devicePolicy); + for (int i = 0; i < mVirtualDisplays.size(); i++) { + mVirtualDisplays.valueAt(i).getWindowPolicyController() + .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT); + } + } + break; + default: + throw new IllegalArgumentException("Device policy " + policyType + + " cannot be changed at runtime. "); + } + } + + @Override // Binder call + @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) { super.createVirtualDpad_enforcePermission(); Objects.requireNonNull(config); @@ -787,6 +819,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mParams.dump(fout, " "); fout.println(" mVirtualDisplayIds: "); synchronized (mVirtualDeviceLock) { + fout.println(" mDevicePolicies: " + mDevicePolicies); for (int i = 0; i < mVirtualDisplays.size(); i++) { fout.println(" " + mVirtualDisplays.keyAt(i)); } @@ -813,9 +846,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub this::onSecureWindowShown, this::shouldInterceptIntent, displayCategories, - mParams.getDevicePolicy( - VirtualDeviceParams.POLICY_TYPE_RECENTS) - == VirtualDeviceParams.DEVICE_POLICY_DEFAULT); + mParams.getDevicePolicy(POLICY_TYPE_RECENTS) == DEVICE_POLICY_DEFAULT); gwpc.registerRunningAppsChangedListener(/* listener= */ this); return gwpc; } diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 557e4ac843d2..838aae8a83c0 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -1409,4 +1409,10 @@ public abstract class PackageManagerInternal { */ public abstract boolean isPackageQuarantined(@NonNull String packageName, @UserIdInt int userId); + + /** + * Return a list of all historical install sessions for the given user. + */ + public abstract ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions( + int userId); } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index b994105de406..f3ad4b443ba3 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -472,6 +472,8 @@ public final class DisplayManagerService extends SystemService { private SensorManager mSensorManager; private BrightnessTracker mBrightnessTracker; + private SmallAreaDetectionController mSmallAreaDetectionController; + // Whether minimal post processing is allowed by the user. @GuardedBy("mSyncRoot") @@ -738,6 +740,8 @@ public final class DisplayManagerService extends SystemService { filter.addAction(Intent.ACTION_DOCK_EVENT); mContext.registerReceiver(mIdleModeReceiver, filter); + + mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext); } @VisibleForTesting @@ -3128,6 +3132,9 @@ public final class DisplayManagerService extends SystemService { pw.println(); mDisplayModeDirector.dump(pw); mBrightnessSynchronizer.dump(pw); + if (mSmallAreaDetectionController != null) { + mSmallAreaDetectionController.dump(pw); + } } private static float[] getFloatArray(TypedArray array) { diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java new file mode 100644 index 000000000000..adaa5390cb9b --- /dev/null +++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2023 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.display; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.PackageManagerInternal; +import android.provider.DeviceConfig; +import android.provider.DeviceConfigInterface; +import android.util.ArrayMap; +import android.util.SparseArray; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Map; + +final class SmallAreaDetectionController { + private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds); + private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold); + + // TODO(b/281720315): Move this to DeviceConfig once server side ready. + private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST = + "small_area_detection_allowlist"; + + private final Object mLock = new Object(); + private final Context mContext; + private final PackageManagerInternal mPackageManager; + private final UserManagerInternal mUserManager; + @GuardedBy("mLock") + private final Map<String, Float> mAllowPkgMap = new ArrayMap<>(); + // TODO(b/298722189): Update allowlist when user changes + @GuardedBy("mLock") + private int[] mUserIds; + + static SmallAreaDetectionController create(@NonNull Context context) { + final SmallAreaDetectionController controller = + new SmallAreaDetectionController(context, DeviceConfigInterface.REAL); + final String property = DeviceConfigInterface.REAL.getProperty( + DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_SMALL_AREA_DETECTION_ALLOWLIST); + controller.updateAllowlist(property); + return controller; + } + + @VisibleForTesting + SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) { + mContext = context; + mPackageManager = LocalServices.getService(PackageManagerInternal.class); + mUserManager = LocalServices.getService(UserManagerInternal.class); + deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + BackgroundThread.getExecutor(), + new SmallAreaDetectionController.OnPropertiesChangedListener()); + mPackageManager.getPackageList(new PackageReceiver()); + } + + @VisibleForTesting + void updateAllowlist(@Nullable String property) { + synchronized (mLock) { + mAllowPkgMap.clear(); + if (property != null) { + final String[] mapStrings = property.split(","); + for (String mapString : mapStrings) putToAllowlist(mapString); + } else { + final String[] defaultMapStrings = mContext.getResources() + .getStringArray(R.array.config_smallAreaDetectionAllowlist); + for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString); + } + updateSmallAreaDetection(); + } + } + + @GuardedBy("mLock") + private void putToAllowlist(String rowData) { + // Data format: package:threshold - e.g. "com.abc.music:0.05" + final String[] items = rowData.split(":"); + if (items.length == 2) { + try { + final String pkg = items[0]; + final float threshold = Float.valueOf(items[1]); + mAllowPkgMap.put(pkg, threshold); + } catch (Exception e) { + // Just skip if items[1] - the threshold is not parsable number + } + } + } + + @GuardedBy("mLock") + private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) { + for (int i = 0; i < mUserIds.length; i++) { + final int userId = mUserIds[i]; + final int uid = mPackageManager.getPackageUid(pkg, 0, userId); + if (uid > 0) list.put(uid, threshold); + } + } + + @GuardedBy("mLock") + private void updateSmallAreaDetection() { + if (mAllowPkgMap.isEmpty()) return; + + mUserIds = mUserManager.getUserIds(); + + final SparseArray<Float> uidThresholdList = new SparseArray<>(); + for (String pkg : mAllowPkgMap.keySet()) { + final float threshold = mAllowPkgMap.get(pkg); + updateUidListForAllUsers(uidThresholdList, pkg, threshold); + } + + final int[] uids = new int[uidThresholdList.size()]; + final float[] thresholds = new float[uidThresholdList.size()]; + for (int i = 0; i < uidThresholdList.size(); i++) { + uids[i] = uidThresholdList.keyAt(i); + thresholds[i] = uidThresholdList.valueAt(i); + } + updateSmallAreaDetection(uids, thresholds); + } + + @VisibleForTesting + void updateSmallAreaDetection(int[] uids, float[] thresholds) { + nativeUpdateSmallAreaDetection(uids, thresholds); + } + + void setSmallAreaDetectionThreshold(int uid, float threshold) { + nativeSetSmallAreaDetectionThreshold(uid, threshold); + } + + void dump(PrintWriter pw) { + pw.println("Small area detection allowlist"); + pw.println(" Packages:"); + synchronized (mLock) { + for (String pkg : mAllowPkgMap.keySet()) { + pw.println(" " + pkg + " threshold = " + mAllowPkgMap.get(pkg)); + } + pw.println(" mUserIds=" + Arrays.toString(mUserIds)); + } + } + + private class OnPropertiesChangedListener implements DeviceConfig.OnPropertiesChangedListener { + public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { + if (properties.getKeyset().contains(KEY_SMALL_AREA_DETECTION_ALLOWLIST)) { + updateAllowlist( + properties.getString(KEY_SMALL_AREA_DETECTION_ALLOWLIST, null /*default*/)); + } + } + } + + private final class PackageReceiver implements PackageManagerInternal.PackageListObserver { + @Override + public void onPackageAdded(@NonNull String packageName, int uid) { + synchronized (mLock) { + if (mAllowPkgMap.containsKey(packageName)) { + setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName)); + } + } + } + } +} diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 1908e4dff234..dcac8c98d19f 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -13,3 +13,10 @@ flag { description: "This flag controls the polite notification feature" bug: "270456865" } + +flag { + name: "refactor_attention_helper" + namespace: "systemui" + description: "This flag controls the refactoring of NMS to NotificationAttentionHelper" + bug: "291907312" +} diff --git a/services/core/java/com/android/server/pm/PackageArchiverService.java b/services/core/java/com/android/server/pm/PackageArchiverService.java index 9c31dc9a1215..29f49c874640 100644 --- a/services/core/java/com/android/server/pm/PackageArchiverService.java +++ b/services/core/java/com/android/server/pm/PackageArchiverService.java @@ -17,17 +17,26 @@ package com.android.server.pm; import static android.content.pm.PackageManager.DELETE_KEEP_DATA; +import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE; +import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.app.AppOpsManager; +import android.app.BroadcastOptions; import android.content.Context; +import android.content.Intent; import android.content.IntentSender; import android.content.pm.IPackageArchiverService; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; +import android.content.pm.PackageArchiver; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.os.Binder; +import android.os.Bundle; import android.os.ParcelableException; import android.os.UserHandle; import android.text.TextUtils; @@ -36,6 +45,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.server.pm.pkg.ArchiveState; import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageUserStateInternal; import java.nio.file.Path; import java.util.ArrayList; @@ -51,7 +61,12 @@ import java.util.Objects; */ public class PackageArchiverService extends IPackageArchiverService.Stub { - private static final String TAG = "PackageArchiver"; + /** + * The maximum time granted for an app store to start a foreground service when unarchival + * is requested. + */ + // TODO(b/297358628) Make this configurable through a flag. + private static final int DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS = 120 * 1000; private final Context mContext; private final PackageManagerService mPm; @@ -82,17 +97,14 @@ public class PackageArchiverService extends IPackageArchiverService.Stub { snapshot.enforceCrossUserPermission(binderUid, userId, true, true, "archiveApp"); verifyCaller(providedUid, binderUid); - PackageStateInternal ps = getPackageState(packageName, snapshot, binderUid, userId); - verifyInstaller(packageName, ps); - - // TODO(b/291569242) Verify that this list is not empty and return failure with - // intentsender - List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList( - ps.getPackageName(), - new UserHandle(userId)); - - // TODO(b/282952870) Bug: should happen after the uninstall completes successfully - storeArchiveState(ps, mainActivities, userId); + ArchiveState archiveState; + try { + archiveState = createArchiveState(packageName, userId); + // TODO(b/282952870) Should be reverted if uninstall fails/cancels + storeArchiveState(packageName, archiveState, userId); + } catch (PackageManager.NameNotFoundException e) { + throw new ParcelableException(e); + } // TODO(b/278553670) Add special strings for the delete dialog mPm.mInstallerService.uninstall( @@ -100,25 +112,154 @@ public class PackageArchiverService extends IPackageArchiverService.Stub { callerPackageName, DELETE_KEEP_DATA, intentSender, userId); } - private static void verifyInstaller(String packageName, PackageStateInternal ps) { - if (ps.getInstallSource().mUpdateOwnerPackageName == null - && ps.getInstallSource().mInstallerPackageName == null) { + private ArchiveState createArchiveState(String packageName, int userId) + throws PackageManager.NameNotFoundException { + PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(), + Binder.getCallingUid(), userId); + String responsibleInstallerPackage = getResponsibleInstallerPackage(ps); + if (responsibleInstallerPackage == null) { + throw new PackageManager.NameNotFoundException( + TextUtils.formatSimple("No installer found to archive app %s.", + packageName)); + } + + List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps, userId); + List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(); + for (int i = 0; i < mainActivities.size(); i++) { + // TODO(b/278553670) Extract and store launcher icons + ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( + mainActivities.get(i).getLabel().toString(), + Path.of("/TODO"), null); + archiveActivityInfos.add(activityInfo); + } + + return new ArchiveState(archiveActivityInfos, responsibleInstallerPackage); + } + + @Override + public void requestUnarchive( + @NonNull String packageName, + @NonNull String callerPackageName, + @NonNull UserHandle userHandle) { + Objects.requireNonNull(packageName); + Objects.requireNonNull(callerPackageName); + Objects.requireNonNull(userHandle); + + Computer snapshot = mPm.snapshotComputer(); + int userId = userHandle.getIdentifier(); + int binderUid = Binder.getCallingUid(); + int providedUid = snapshot.getPackageUid(callerPackageName, 0, userId); + snapshot.enforceCrossUserPermission(binderUid, userId, true, true, + "unarchiveApp"); + verifyCaller(providedUid, binderUid); + PackageStateInternal ps; + try { + ps = getPackageState(packageName, snapshot, binderUid, userId); + verifyArchived(ps, userId); + } catch (PackageManager.NameNotFoundException e) { + throw new ParcelableException(e); + } + String installerPackage = getResponsibleInstallerPackage(ps); + if (installerPackage == null) { throw new ParcelableException( new PackageManager.NameNotFoundException( - TextUtils.formatSimple("No installer found to archive app %s.", + TextUtils.formatSimple("No installer found to unarchive app %s.", packageName))); } + + mPm.mHandler.post(() -> unarchiveInternal(packageName, userHandle, installerPackage)); + } + + private void verifyArchived(PackageStateInternal ps, int userId) + throws PackageManager.NameNotFoundException { + PackageUserStateInternal userState = ps.getUserStateOrDefault(userId); + // TODO(b/288142708) Check for isInstalled false here too. + if (userState.getArchiveState() == null) { + throw new PackageManager.NameNotFoundException( + TextUtils.formatSimple("Package %s is not currently archived.", + ps.getPackageName())); + } + } + + @RequiresPermission( + allOf = { + Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, + android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}, + conditional = true) + private void unarchiveInternal(String packageName, UserHandle userHandle, + String installerPackage) { + int userId = userHandle.getIdentifier(); + Intent unarchiveIntent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE); + unarchiveIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + unarchiveIntent.putExtra(PackageArchiver.EXTRA_UNARCHIVE_PACKAGE_NAME, packageName); + unarchiveIntent.putExtra(PackageArchiver.EXTRA_UNARCHIVE_ALL_USERS, + userId == UserHandle.USER_ALL); + unarchiveIntent.setPackage(installerPackage); + + // If the unarchival is requested for all users, the current user is used for unarchival. + UserHandle userForUnarchival = userId == UserHandle.USER_ALL + ? UserHandle.of(mPm.mUserManager.getCurrentUserId()) + : userHandle; + mContext.sendOrderedBroadcastAsUser( + unarchiveIntent, + userForUnarchival, + /* receiverPermission = */ null, + AppOpsManager.OP_NONE, + createUnarchiveOptions(), + /* resultReceiver= */ null, + /* scheduler= */ null, + /* initialCode= */ 0, + /* initialData= */ null, + /* initialExtras= */ null); + } + + private List<LauncherActivityInfo> getLauncherActivityInfos(PackageStateInternal ps, + int userId) throws PackageManager.NameNotFoundException { + List<LauncherActivityInfo> mainActivities = + Binder.withCleanCallingIdentity(() -> getLauncherApps().getActivityList( + ps.getPackageName(), + new UserHandle(userId))); + if (mainActivities.isEmpty()) { + throw new PackageManager.NameNotFoundException( + TextUtils.formatSimple("The app %s does not have a main activity.", + ps.getPackageName())); + } + + return mainActivities; + } + + @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, + android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) + private Bundle createUnarchiveOptions() { + BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setTemporaryAppAllowlist(getUnarchiveForegroundTimeout(), + TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + REASON_PACKAGE_UNARCHIVE, ""); + return options.toBundle(); + } + + private static int getUnarchiveForegroundTimeout() { + return DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS; + } + + private String getResponsibleInstallerPackage(PackageStateInternal ps) { + return ps.getInstallSource().mUpdateOwnerPackageName == null + ? ps.getInstallSource().mInstallerPackageName + : ps.getInstallSource().mUpdateOwnerPackageName; } @NonNull private static PackageStateInternal getPackageState(String packageName, - Computer snapshot, int callingUid, int userId) { + Computer snapshot, int callingUid, int userId) + throws PackageManager.NameNotFoundException { PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid, userId); if (ps == null) { - throw new ParcelableException( - new PackageManager.NameNotFoundException( - TextUtils.formatSimple("Package %s not found.", packageName))); + throw new PackageManager.NameNotFoundException( + TextUtils.formatSimple("Package %s not found.", packageName)); } return ps; } @@ -130,38 +271,25 @@ public class PackageArchiverService extends IPackageArchiverService.Stub { return mLauncherApps; } - private void storeArchiveState(PackageStateInternal ps, - List<LauncherActivityInfo> mainActivities, int userId) { - List<ArchiveActivityInfo> activityInfos = new ArrayList<>(); - for (int i = 0; i < mainActivities.size(); i++) { - // TODO(b/278553670) Extract and store launcher icons - ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( - mainActivities.get(i).getLabel().toString(), - Path.of("/TODO"), null); - activityInfos.add(activityInfo); - } - - InstallSource installSource = ps.getInstallSource(); - String installerPackageName = installSource.mUpdateOwnerPackageName != null - ? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName; - + private void storeArchiveState(String packageName, ArchiveState archiveState, int userId) + throws PackageManager.NameNotFoundException { synchronized (mPm.mLock) { - PackageSetting packageSetting = getPackageSettingLocked(ps.getPackageName(), userId); + PackageSetting packageSetting = getPackageSettingLocked(packageName, userId); packageSetting .modifyUserState(userId) - .setArchiveState(new ArchiveState(activityInfos, installerPackageName)); + .setArchiveState(archiveState); } } @NonNull @GuardedBy("mPm.mLock") - private PackageSetting getPackageSettingLocked(String packageName, int userId) { + private PackageSetting getPackageSettingLocked(String packageName, int userId) + throws PackageManager.NameNotFoundException { PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); // Shouldn't happen, we already verify presence of the package in getPackageState() if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) { - throw new ParcelableException( - new PackageManager.NameNotFoundException( - TextUtils.formatSimple("Package %s not found.", packageName))); + throw new PackageManager.NameNotFoundException( + TextUtils.formatSimple("Package %s not found.", packageName)); } return ps; } diff --git a/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java new file mode 100644 index 000000000000..d40a7157253b --- /dev/null +++ b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.content.pm.PackageInstaller.PreapprovalDetails; +import android.content.pm.PackageInstaller.SessionInfo; +import android.content.pm.PackageInstaller.SessionParams; + +import com.android.internal.util.IndentingPrintWriter; + +import java.io.CharArrayWriter; +import java.io.File; + +/** + * A historical session object that stores minimal session info. + */ +public final class PackageInstallerHistoricalSession { + public final int sessionId; + public final int userId; + private final String mParams; + private final long mCreatedMillis; + + private final File mStageDir; + private final String mStageCid; + + private final long mUpdatedMillis; + + private final long mCommittedMillis; + + private final int mOriginalInstallerUid; + + private final String mOriginalInstallerPackageName; + + private final int mInstallerUid; + + private final InstallSource mInstallSource; + + private final float mClientProgress; + + private final float mProgress; + private final boolean mSealed; + + private final boolean mPreapprovalRequested; + private final boolean mCommitted; + + private final boolean mStageDirInUse; + + private final boolean mPermissionsManuallyAccepted; + + private final int mFinalStatus; + private final String mFinalMessage; + + private final int mFds; + private final int mBridges; + + private final String mPreapprovalDetails; + private final int mParentSessionId; + private final boolean mDestroyed; + private final int[] mChildSessionIds; + private final boolean mSessionApplied; + private final boolean mSessionReady; + private final boolean mSessionFailed; + private final int mSessionErrorCode; + private final String mSessionErrorMessage; + + PackageInstallerHistoricalSession(int sessionId, int userId, int originalInstallerUid, + String originalInstallerPackageName, InstallSource installSource, int installerUid, + long createdMillis, long updatedMillis, long committedMillis, File stageDir, + String stageCid, float clientProgress, float progress, boolean committed, + boolean preapprovalRequested, boolean sealed, boolean permissionsManuallyAccepted, + boolean stageDirInUse, boolean destroyed, int fds, int bridges, int finalStatus, + String finalMessage, SessionParams params, int parentSessionId, + int[] childSessionIds, boolean sessionApplied, boolean sessionFailed, + boolean sessionReady, int sessionErrorCode, String sessionErrorMessage, + PreapprovalDetails preapprovalDetails) { + this.sessionId = sessionId; + this.userId = userId; + this.mOriginalInstallerUid = originalInstallerUid; + this.mOriginalInstallerPackageName = originalInstallerPackageName; + this.mInstallSource = installSource; + this.mInstallerUid = installerUid; + this.mCreatedMillis = createdMillis; + this.mUpdatedMillis = updatedMillis; + this.mCommittedMillis = committedMillis; + this.mStageDir = stageDir; + this.mStageCid = stageCid; + this.mClientProgress = clientProgress; + this.mProgress = progress; + this.mCommitted = committed; + this.mPreapprovalRequested = preapprovalRequested; + this.mSealed = sealed; + this.mPermissionsManuallyAccepted = permissionsManuallyAccepted; + this.mStageDirInUse = stageDirInUse; + this.mDestroyed = destroyed; + this.mFds = fds; + this.mBridges = bridges; + this.mFinalStatus = finalStatus; + this.mFinalMessage = finalMessage; + + CharArrayWriter writer = new CharArrayWriter(); + IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + params.dump(pw); + this.mParams = writer.toString(); + + this.mParentSessionId = parentSessionId; + this.mChildSessionIds = childSessionIds; + this.mSessionApplied = sessionApplied; + this.mSessionFailed = sessionFailed; + this.mSessionReady = sessionReady; + this.mSessionErrorCode = sessionErrorCode; + this.mSessionErrorMessage = sessionErrorMessage; + if (preapprovalDetails != null) { + this.mPreapprovalDetails = preapprovalDetails.toString(); + } else { + this.mPreapprovalDetails = null; + } + } + + void dump(IndentingPrintWriter pw) { + pw.println("Session " + sessionId + ":"); + pw.increaseIndent(); + + pw.printPair("userId", userId); + pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid); + pw.printPair("mOriginalInstallerPackageName", mOriginalInstallerPackageName); + pw.printPair("installerPackageName", mInstallSource.mInstallerPackageName); + pw.printPair("installInitiatingPackageName", mInstallSource.mInitiatingPackageName); + pw.printPair("installOriginatingPackageName", mInstallSource.mOriginatingPackageName); + pw.printPair("mInstallerUid", mInstallerUid); + pw.printPair("createdMillis", mCreatedMillis); + pw.printPair("updatedMillis", mUpdatedMillis); + pw.printPair("committedMillis", mCommittedMillis); + pw.printPair("stageDir", mStageDir); + pw.printPair("stageCid", mStageCid); + pw.println(); + + pw.print(mParams); + + pw.printPair("mClientProgress", mClientProgress); + pw.printPair("mProgress", mProgress); + pw.printPair("mCommitted", mCommitted); + pw.printPair("mPreapprovalRequested", mPreapprovalRequested); + pw.printPair("mSealed", mSealed); + pw.printPair("mPermissionsManuallyAccepted", mPermissionsManuallyAccepted); + pw.printPair("mStageDirInUse", mStageDirInUse); + pw.printPair("mDestroyed", mDestroyed); + pw.printPair("mFds", mFds); + pw.printPair("mBridges", mBridges); + pw.printPair("mFinalStatus", mFinalStatus); + pw.printPair("mFinalMessage", mFinalMessage); + pw.printPair("mParentSessionId", mParentSessionId); + pw.printPair("mChildSessionIds", mChildSessionIds); + pw.printPair("mSessionApplied", mSessionApplied); + pw.printPair("mSessionFailed", mSessionFailed); + pw.printPair("mSessionReady", mSessionReady); + pw.printPair("mSessionErrorCode", mSessionErrorCode); + pw.printPair("mSessionErrorMessage", mSessionErrorMessage); + pw.printPair("mPreapprovalDetails", mPreapprovalDetails); + pw.println(); + + pw.decreaseIndent(); + } + + /** + * Generates a {@link SessionInfo} object. + */ + public SessionInfo generateInfo() { + final SessionInfo info = new SessionInfo(); + info.sessionId = sessionId; + info.userId = userId; + info.installerPackageName = mInstallSource.mInstallerPackageName; + info.installerAttributionTag = mInstallSource.mInstallerAttributionTag; + info.progress = mProgress; + info.sealed = mSealed; + info.isCommitted = mCommitted; + info.isPreapprovalRequested = mPreapprovalRequested; + + info.parentSessionId = mParentSessionId; + info.childSessionIds = mChildSessionIds; + info.isSessionApplied = mSessionApplied; + info.isSessionReady = mSessionReady; + info.isSessionFailed = mSessionFailed; + info.setSessionErrorCode(mSessionErrorCode, mSessionErrorMessage); + info.createdMillis = mCreatedMillis; + info.updatedMillis = mUpdatedMillis; + info.installerUid = mInstallerUid; + return info; + } +} diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 10cd51a717cb..e3602565ef5b 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -111,7 +111,6 @@ import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParserException; -import java.io.CharArrayWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -230,7 +229,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements /** Historical sessions kept around for debugging purposes */ @GuardedBy("mSessions") - private final List<String> mHistoricalSessions = new ArrayList<>(); + private final List<PackageInstallerHistoricalSession> mHistoricalSessions = new ArrayList<>(); @GuardedBy("mSessions") private final SparseIntArray mHistoricalSessionsByInstaller = new SparseIntArray(); @@ -570,14 +569,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements @GuardedBy("mSessions") private void addHistoricalSessionLocked(PackageInstallerSession session) { - CharArrayWriter writer = new CharArrayWriter(); - IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - session.dump(pw); if (mHistoricalSessions.size() > HISTORICAL_SESSIONS_THRESHOLD) { Slog.d(TAG, "Historical sessions size reaches threshold, clear the oldest"); mHistoricalSessions.subList(0, HISTORICAL_CLEAR_SIZE).clear(); } - mHistoricalSessions.add(writer.toString()); + mHistoricalSessions.add(session.createHistoricalSession()); int installerUid = session.getInstallerUid(); // Increment the number of sessions by this installerUid. @@ -1223,6 +1219,24 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements return new ParceledListSlice<>(result); } + ParceledListSlice<SessionInfo> getHistoricalSessions(int userId) { + final int callingUid = Binder.getCallingUid(); + final Computer snapshot = mPm.snapshotComputer(); + snapshot.enforceCrossUserPermission(callingUid, userId, true, false, "getAllSessions"); + + final List<SessionInfo> result = new ArrayList<>(); + synchronized (mSessions) { + for (int i = 0; i < mHistoricalSessions.size(); i++) { + final PackageInstallerHistoricalSession session = mHistoricalSessions.get(i); + if (userId == UserHandle.USER_ALL || session.userId == userId) { + result.add(session.generateInfo()); + } + } + } + result.removeIf(info -> shouldFilterSession(snapshot, callingUid, info)); + return new ParceledListSlice<>(result); + } + @Override public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags, IntentSender statusReceiver, int userId) { @@ -1837,7 +1851,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements pw.increaseIndent(); N = mHistoricalSessions.size(); for (int i = 0; i < N; i++) { - pw.print(mHistoricalSessions.get(i)); + mHistoricalSessions.get(i).dump(pw); pw.println(); } pw.println(); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 15540725095b..1bdade2df6dc 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1169,6 +1169,25 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + PackageInstallerHistoricalSession createHistoricalSession() { + final float progress; + final float clientProgress; + synchronized (mProgressLock) { + progress = mProgress; + clientProgress = mClientProgress; + } + synchronized (mLock) { + return new PackageInstallerHistoricalSession(sessionId, userId, mOriginalInstallerUid, + mOriginalInstallerPackageName, mInstallSource, mInstallerUid, createdMillis, + updatedMillis, committedMillis, stageDir, stageCid, clientProgress, progress, + isCommitted(), isPreapprovalRequested(), mSealed, mPermissionsManuallyAccepted, + mStageDirInUse, mDestroyed, mFds.size(), mBridges.size(), mFinalStatus, + mFinalMessage, params, mParentSessionId, getChildSessionIdsLocked(), + mSessionApplied, mSessionFailed, mSessionReady, mSessionErrorCode, + mSessionErrorMessage, mPreapprovalDetails); + } + } + /** * Returns {@code true} if the {@link SessionInfo} object should be produced with potentially * sensitive data scrubbed from its fields. diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 7390bbe5cac9..bb4fd66e995c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -6976,6 +6976,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService return mDistractingPackageHelper.getDistractingPackageRestrictionsAsUser(snapshot, packageNames, userId, callingUid); } + + @Override + public ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions(int userId) { + return mInstallerService.getHistoricalSessions(userId); + } } private void setEnabledOverlayPackages(@UserIdInt int userId, diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index cbac39a5e0f2..63794d57fabc 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3354,6 +3354,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { return true; } break; + case KeyEvent.KEYCODE_DEL: + case KeyEvent.KEYCODE_GRAVE: + if (firstDown && event.isMetaPressed()) { + logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK); + injectBackGesture(event.getDownTime()); + return true; + } case KeyEvent.KEYCODE_DPAD_UP: if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); @@ -3365,9 +3372,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { } break; case KeyEvent.KEYCODE_DPAD_LEFT: - if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { - enterStageSplitFromRunningApp(true /* leftOrTop */); - logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION); + if (firstDown && event.isMetaPressed()) { + if (event.isCtrlPressed()) { + enterStageSplitFromRunningApp(true /* leftOrTop */); + logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION); + } else { + logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK); + injectBackGesture(event.getDownTime()); + } return true; } break; @@ -3630,6 +3642,25 @@ public class PhoneWindowManager implements WindowManagerPolicy { return (metaState & KeyEvent.META_META_ON) != 0; } + @SuppressLint("MissingPermission") + private void injectBackGesture(long downtime) { + // Create and inject down event + KeyEvent downEvent = new KeyEvent(downtime, downtime, KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, + KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, + InputDevice.SOURCE_KEYBOARD); + mInputManager.injectInputEvent(downEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + + + // Create and inject up event + KeyEvent upEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_UP); + mInputManager.injectInputEvent(upEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + + downEvent.recycle(); + upEvent.recycle(); + } + private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) { // First we always handle the home key here, so applications // can never break it, although if keyguard is on, we do let diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java index 3fb845f064e3..e4f960763d54 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java @@ -19,7 +19,7 @@ package com.android.server.vibrator; import android.annotation.Nullable; import android.content.res.Resources; import android.os.VibrationEffect; -import android.os.Vibrator; +import android.os.VibratorInfo; import android.os.vibrator.persistence.ParsedVibration; import android.os.vibrator.persistence.VibrationXmlParser; import android.text.TextUtils; @@ -107,10 +107,10 @@ final class HapticFeedbackCustomization { * @hide */ @Nullable - static SparseArray<VibrationEffect> loadVibrations(Resources res, Vibrator vibrator) + static SparseArray<VibrationEffect> loadVibrations(Resources res, VibratorInfo vibratorInfo) throws CustomizationParserException, IOException { try { - return loadVibrationsInternal(res, vibrator); + return loadVibrationsInternal(res, vibratorInfo); } catch (VibrationXmlParser.VibrationXmlParserException | XmlParserException | XmlPullParserException e) { @@ -121,7 +121,7 @@ final class HapticFeedbackCustomization { @Nullable private static SparseArray<VibrationEffect> loadVibrationsInternal( - Resources res, Vibrator vibrator) throws + Resources res, VibratorInfo vibratorInfo) throws CustomizationParserException, IOException, VibrationXmlParser.VibrationXmlParserException, @@ -175,7 +175,7 @@ final class HapticFeedbackCustomization { throw new CustomizationParserException( "Unable to parse vibration element for effect " + effectId); } - VibrationEffect effect = parsedVibration.resolve(vibrator); + VibrationEffect effect = parsedVibration.resolve(vibratorInfo); if (effect != null) { if (effect.getDuration() == Long.MAX_VALUE) { throw new CustomizationParserException(String.format( diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java index 7c9954391d8c..3d89afaae88f 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java @@ -21,6 +21,7 @@ import android.content.res.Resources; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; +import android.os.VibratorInfo; import android.util.Slog; import android.util.SparseArray; import android.view.HapticFeedbackConstants; @@ -29,8 +30,6 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.IOException; import java.io.PrintWriter; -import java.util.HashSet; -import java.util.Set; /** * Provides the {@link VibrationEffect} and {@link VibrationAttributes} for haptic feedback. @@ -47,7 +46,7 @@ public final class HapticFeedbackVibrationProvider { private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); - private final Vibrator mVibrator; + private final VibratorInfo mVibratorInfo; private final boolean mHapticTextHandleEnabled; // Vibrator effect for haptic feedback during boot when safe mode is enabled. private final VibrationEffect mSafeModeEnabledVibrationEffect; @@ -58,25 +57,25 @@ public final class HapticFeedbackVibrationProvider { /** @hide */ public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) { - this(res, vibrator, loadHapticCustomizations(res, vibrator)); + this(res, vibrator.getInfo()); + } + + /** @hide */ + public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) { + this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo)); } /** @hide */ @VisibleForTesting HapticFeedbackVibrationProvider( Resources res, - Vibrator vibrator, + VibratorInfo vibratorInfo, @Nullable SparseArray<VibrationEffect> hapticCustomizations) { - mVibrator = vibrator; + mVibratorInfo = vibratorInfo; mHapticTextHandleEnabled = res.getBoolean( com.android.internal.R.bool.config_enableHapticTextHandle); - if (hapticCustomizations != null) { - // Clean up the customizations to remove vibrations which may not ever be used due to - // Vibrator properties or other device configurations. - removeUnsupportedVibrations(hapticCustomizations, vibrator); - if (hapticCustomizations.size() == 0) { - hapticCustomizations = null; - } + if (hapticCustomizations != null && hapticCustomizations.size() == 0) { + hapticCustomizations = null; } mHapticCustomizations = hapticCustomizations; @@ -257,7 +256,7 @@ public final class HapticFeedbackVibrationProvider { if (effectHasCustomization(hapticFeedbackId)) { return mHapticCustomizations.get(hapticFeedbackId); } - if (mVibrator.areAllPrimitivesSupported(primitiveId)) { + if (mVibratorInfo.isPrimitiveSupported(primitiveId)) { return VibrationEffect.startComposition() .addPrimitive(primitiveId, primitiveScale) .compose(); @@ -270,9 +269,8 @@ public final class HapticFeedbackVibrationProvider { if (effectHasCustomization(HapticFeedbackConstants.ASSISTANT_BUTTON)) { return mHapticCustomizations.get(HapticFeedbackConstants.ASSISTANT_BUTTON); } - if (mVibrator.areAllPrimitivesSupported( - VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, - VibrationEffect.Composition.PRIMITIVE_TICK)) { + if (mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE) + && mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)) { // quiet ramp, short pause, then sharp tick return VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f) @@ -289,27 +287,12 @@ public final class HapticFeedbackVibrationProvider { @Nullable private static SparseArray<VibrationEffect> loadHapticCustomizations( - Resources res, Vibrator vibrator) { + Resources res, VibratorInfo vibratorInfo) { try { - return HapticFeedbackCustomization.loadVibrations(res, vibrator); + return HapticFeedbackCustomization.loadVibrations(res, vibratorInfo); } catch (IOException | HapticFeedbackCustomization.CustomizationParserException e) { Slog.e(TAG, "Unable to load haptic customizations.", e); return null; } } - - private static void removeUnsupportedVibrations( - SparseArray<VibrationEffect> customizations, Vibrator vibrator) { - Set<Integer> keysToRemove = new HashSet<>(); - for (int i = 0; i < customizations.size(); i++) { - int key = customizations.keyAt(i); - if (!vibrator.areVibrationFeaturesSupported(customizations.get(key))) { - keysToRemove.add(key); - } - } - - for (int key : keysToRemove) { - customizations.remove(key); - } - } } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index e296c7b764e5..ee3d697cddb8 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.hardware.vibrator.IVibrator; import android.os.BatteryStats; import android.os.Binder; @@ -131,6 +132,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private final Object mLock = new Object(); private final Context mContext; + private final Injector mInjector; private final PowerManager.WakeLock mWakeLock; private final IBatteryStats mBatteryStatsService; private final VibratorFrameworkStatsLogger mFrameworkStatsLogger; @@ -162,6 +164,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @GuardedBy("mLock") @Nullable private VibratorInfo mCombinedVibratorInfo; + @GuardedBy("mLock") + @Nullable private HapticFeedbackVibrationProvider mHapticFeedbackVibrationProvider; private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override @@ -201,6 +205,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @VisibleForTesting VibratorManagerService(Context context, Injector injector) { mContext = context; + mInjector = injector; mHandler = injector.createHandler(Looper.myLooper()); mVibrationSettings = new VibrationSettings(mContext, mHandler); @@ -393,7 +398,42 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @Override // Binder call public void vibrate(int uid, int displayId, String opPkg, @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs, String reason, IBinder token) { - vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token); + vibrateWithPermissionCheck(uid, displayId, opPkg, effect, attrs, reason, token); + } + + @Override // Binder call + public void performHapticFeedback( + int uid, int displayId, String opPkg, int constant, boolean always, String reason, + IBinder token) { + performHapticFeedbackInternal(uid, displayId, opPkg, constant, always, reason, token); + } + + /** + * An internal-only version of performHapticFeedback that allows the caller access to the + * {@link HalVibration}. + * The Vibration is only returned if it is ongoing after this method returns. + */ + @VisibleForTesting + @Nullable + HalVibration performHapticFeedbackInternal( + int uid, int displayId, String opPkg, int constant, boolean always, String reason, + IBinder token) { + HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider(); + if (hapticVibrationProvider == null) { + Slog.w(TAG, "performHapticFeedback; haptic vibration provider not ready."); + return null; + } + VibrationEffect effect = hapticVibrationProvider.getVibrationForHapticFeedback(constant); + if (effect == null) { + Slog.w(TAG, "performHapticFeedback; vibration absent for effect " + constant); + return null; + } + CombinedVibration combinedVibration = CombinedVibration.createParallel(effect); + VibrationAttributes attrs = + hapticVibrationProvider.getVibrationAttributesForHapticFeedback( + constant, /* bypassVibrationIntensitySetting= */ always); + return vibrateWithoutPermissionCheck(uid, displayId, opPkg, combinedVibration, attrs, + "performHapticFeedback: " + reason, token); } /** @@ -403,90 +443,107 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ @VisibleForTesting @Nullable - HalVibration vibrateInternal(int uid, int displayId, String opPkg, + HalVibration vibrateWithPermissionCheck(int uid, int displayId, String opPkg, @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs, String reason, IBinder token) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason); try { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, "vibrate"); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.VIBRATE, "vibrate"); + return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } - if (token == null) { - Slog.e(TAG, "token must not be null"); - return null; - } - enforceUpdateAppOpsStatsPermission(uid); - if (!isEffectValid(effect)) { - return null; - } - attrs = fixupVibrationAttributes(attrs, effect); - // Create Vibration.Stats as close to the received request as possible, for tracking. - HalVibration vib = new HalVibration(token, effect, - new Vibration.CallerInfo(attrs, uid, displayId, opPkg, reason)); - fillVibrationFallbacks(vib, effect); + HalVibration vibrateWithoutPermissionCheck(int uid, int displayId, String opPkg, + @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs, + String reason, IBinder token) { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate no perm check, reason = " + reason); + try { + return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } - if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) { - // Force update of user settings before checking if this vibration effect should - // be ignored or scaled. - mVibrationSettings.update(); - } + private HalVibration vibrateInternal(int uid, int displayId, String opPkg, + @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs, + String reason, IBinder token) { + if (token == null) { + Slog.e(TAG, "token must not be null"); + return null; + } + enforceUpdateAppOpsStatsPermission(uid); + if (!isEffectValid(effect)) { + return null; + } + attrs = fixupVibrationAttributes(attrs, effect); + // Create Vibration.Stats as close to the received request as possible, for tracking. + HalVibration vib = new HalVibration(token, effect, + new Vibration.CallerInfo(attrs, uid, displayId, opPkg, reason)); + fillVibrationFallbacks(vib, effect); - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "Starting vibrate for vibration " + vib.id); - } + if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) { + // Force update of user settings before checking if this vibration effect should + // be ignored or scaled. + mVibrationSettings.update(); + } - // Check if user settings or DnD is set to ignore this vibration. - Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo); + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "Starting vibrate for vibration " + vib.id); + } - // Check if ongoing vibration is more important than this vibration. - if (vibrationEndInfo == null) { - vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib); - } + // Check if user settings or DnD is set to ignore this vibration. + Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo); - // If not ignored so far then try to start this vibration. - if (vibrationEndInfo == null) { - final long ident = Binder.clearCallingIdentity(); - try { - if (mCurrentExternalVibration != null) { - mCurrentExternalVibration.mute(); + // Check if ongoing vibration is more important than this vibration. + if (vibrationEndInfo == null) { + vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib); + } + + // If not ignored so far then try to start this vibration. + if (vibrationEndInfo == null) { + final long ident = Binder.clearCallingIdentity(); + try { + if (mCurrentExternalVibration != null) { + mCurrentExternalVibration.mute(); + vib.stats.reportInterruptedAnotherVibration( + mCurrentExternalVibration.callerInfo); + endExternalVibrateLocked( + new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED, + vib.callerInfo), + /* continueExternalControl= */ false); + } else if (mCurrentVibration != null) { + if (mCurrentVibration.getVibration().canPipelineWith(vib)) { + // Don't cancel the current vibration if it's pipeline-able. + // Note that if there is a pending next vibration that can't be + // pipelined, it will have already cancelled the current one, so we + // don't need to consider it here as well. + if (DEBUG) { + Slog.d(TAG, "Pipelining vibration " + vib.id); + } + } else { vib.stats.reportInterruptedAnotherVibration( - mCurrentExternalVibration.callerInfo); - endExternalVibrateLocked( + mCurrentVibration.getVibration().callerInfo); + mCurrentVibration.notifyCancelled( new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED, vib.callerInfo), - /* continueExternalControl= */ false); - } else if (mCurrentVibration != null) { - if (mCurrentVibration.getVibration().canPipelineWith(vib)) { - // Don't cancel the current vibration if it's pipeline-able. - // Note that if there is a pending next vibration that can't be - // pipelined, it will have already cancelled the current one, so we - // don't need to consider it here as well. - if (DEBUG) { - Slog.d(TAG, "Pipelining vibration " + vib.id); - } - } else { - vib.stats.reportInterruptedAnotherVibration( - mCurrentVibration.getVibration().callerInfo); - mCurrentVibration.notifyCancelled( - new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED, - vib.callerInfo), - /* immediate= */ false); - } + /* immediate= */ false); } - vibrationEndInfo = startVibrationLocked(vib); - } finally { - Binder.restoreCallingIdentity(ident); } + vibrationEndInfo = startVibrationLocked(vib); + } finally { + Binder.restoreCallingIdentity(ident); } + } - // Ignored or failed to start the vibration, end it and report metrics right away. - if (vibrationEndInfo != null) { - endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ true); - } - return vib; + // Ignored or failed to start the vibration, end it and report metrics right away. + if (vibrationEndInfo != null) { + endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ true); } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + return vib; } } @@ -1315,6 +1372,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return new VibratorController(vibratorId, listener); } + HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider( + Resources resources, VibratorInfo vibratorInfo) { + return new HapticFeedbackVibrationProvider(resources, vibratorInfo); + } + void addService(String name, IBinder service) { ServiceManager.addService(name, service); } @@ -1831,6 +1893,22 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + private HapticFeedbackVibrationProvider getHapticVibrationProvider() { + synchronized (mLock) { + // Used a cached haptic vibration provider if one exists. + if (mHapticFeedbackVibrationProvider != null) { + return mHapticFeedbackVibrationProvider; + } + VibratorInfo combinedVibratorInfo = getCombinedVibratorInfo(); + if (combinedVibratorInfo == null) { + return null; + } + return mHapticFeedbackVibrationProvider = + mInjector.createHapticFeedbackVibrationProvider( + mContext.getResources(), combinedVibratorInfo); + } + } + private VibratorInfo getCombinedVibratorInfo() { synchronized (mLock) { // Used a cached resolving vibrator if one exists. @@ -2068,6 +2146,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if ("cancel".equals(cmd)) { return runCancel(); } + if ("feedback".equals(cmd)) { + return runHapticFeedback(); + } return handleDefaultCommands(cmd); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); @@ -2098,16 +2179,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // only cancel background vibrations. IBinder deathBinder = commonOptions.background ? VibratorManagerService.this : mShellCallbacksToken; - HalVibration vib = vibrateInternal(Binder.getCallingUid(), Display.DEFAULT_DISPLAY, - SHELL_PACKAGE_NAME, combined, attrs, commonOptions.description, deathBinder); - if (vib != null && !commonOptions.background) { - try { - // Waits for the client vibration to finish, but the VibrationThread may still - // do cleanup after this. - vib.waitForEnd(); - } catch (InterruptedException e) { - } - } + HalVibration vib = vibrateWithPermissionCheck(Binder.getCallingUid(), + Display.DEFAULT_DISPLAY, SHELL_PACKAGE_NAME, combined, attrs, + commonOptions.description, deathBinder); + maybeWaitOnVibration(vib, commonOptions); } private int runMono() { @@ -2155,6 +2230,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return 0; } + private int runHapticFeedback() { + CommonOptions commonOptions = new CommonOptions(); + int constant = Integer.parseInt(getNextArgRequired()); + + IBinder deathBinder = commonOptions.background ? VibratorManagerService.this + : mShellCallbacksToken; + HalVibration vib = performHapticFeedbackInternal(Binder.getCallingUid(), + Display.DEFAULT_DISPLAY, SHELL_PACKAGE_NAME, constant, + /* always= */ commonOptions.force, /* reason= */ commonOptions.description, + deathBinder); + maybeWaitOnVibration(vib, commonOptions); + + return 0; + } + private VibrationEffect nextEffect() { VibrationEffect.Composition composition = VibrationEffect.startComposition(); String nextArg; @@ -2364,6 +2454,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + private void maybeWaitOnVibration(HalVibration vib, CommonOptions commonOptions) { + if (vib != null && !commonOptions.background) { + try { + // Waits for the client vibration to finish, but the VibrationThread may still + // do cleanup after this. + vib.waitForEnd(); + } catch (InterruptedException e) { + } + } + } + @Override public void onHelp() { try (PrintWriter pw = getOutPrintWriter();) { @@ -2389,6 +2490,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { pw.println(" XML containing a single effect it runs on all vibrators in sync."); pw.println(" cancel"); pw.println(" Cancels any active vibration"); + pw.println(" feedback [-f] [-d <description>] <constant>"); + pw.println(" Performs a haptic feedback with the given constant."); + pw.println(" The force (-f) option enables the `always` configuration, which"); + pw.println(" plays the haptic irrespective of the vibration intensity settings"); pw.println(""); pw.println("Effect commands:"); pw.println(" oneshot [-w delay] [-a] <duration> [<amplitude>]"); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 53d1adfefbb9..fd42077bed7d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6183,6 +6183,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public void onPackageReplaced(ApplicationInfo aInfo) { synchronized (mGlobalLock) { + // In case if setWindowManager hasn't been called yet when booting. + if (mRootWindowContainer == null) return; mRootWindowContainer.updateActivityApplicationInfo(aInfo); } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 1871cf6175c5..707b7799460e 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR; import static android.view.Display.TYPE_INTERNAL; import static android.view.InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE; import static android.view.InsetsFrameProvider.SOURCE_CONTAINER_BOUNDS; @@ -1201,8 +1202,8 @@ public class DisplayPolicy { throw new IllegalArgumentException("IME insets must be provided by a window."); } - if (mNavigationBar != null && navigationBarPosition(displayFrames.mRotation) - == NAV_BAR_BOTTOM) { + if (!ENABLE_HIDE_IME_CAPTION_BAR && mNavigationBar != null + && navigationBarPosition(displayFrames.mRotation) == NAV_BAR_BOTTOM) { // In gesture navigation, nav bar frame is larger than frame to calculate insets. // IME should not provide frame which is smaller than the nav bar frame. Otherwise, // nav bar might be overlapped with the content of the client when IME is shown. diff --git a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java index e646f14a3e13..106e142cc342 100644 --- a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java +++ b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java @@ -21,6 +21,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION import android.annotation.NonNull; import android.annotation.Nullable; import android.os.RemoteException; +import android.os.Trace; import android.util.Slog; import android.view.IDisplayChangeWindowCallback; import android.window.DisplayAreaInfo; @@ -40,6 +41,7 @@ import java.util.List; public class RemoteDisplayChangeController { private static final String TAG = "RemoteDisplayChangeController"; + private static final String REMOTE_DISPLAY_CHANGE_TRACE_TAG = "RemoteDisplayChange"; private static final int REMOTE_DISPLAY_CHANGE_TIMEOUT_MS = 800; @@ -82,6 +84,10 @@ public class RemoteDisplayChangeController { } mCallbacks.add(callback); + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.beginAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode()); + } + if (newDisplayAreaInfo != null) { ProtoLog.v(WM_DEBUG_CONFIGURATION, "Starting remote display change: " @@ -122,6 +128,10 @@ public class RemoteDisplayChangeController { mCallbacks.clear(); } callback.onContinueRemoteDisplayChange(null /* transaction */); + + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode()); + } } } } @@ -137,13 +147,23 @@ public class RemoteDisplayChangeController { for (int i = 0; i < idx; ++i) { // Expect remote callbacks in order. If they don't come in order, then force // ordering by continuing everything up until this one with empty transactions. - mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */); + ContinueRemoteDisplayChangeCallback currentCallback = mCallbacks.get(i); + currentCallback.onContinueRemoteDisplayChange(null /* transaction */); + + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, + currentCallback.hashCode()); + } } mCallbacks.subList(0, idx + 1).clear(); if (mCallbacks.isEmpty()) { mService.mH.removeCallbacks(mTimeoutRunnable); } callback.onContinueRemoteDisplayChange(transaction); + + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode()); + } } } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 2a995b24ef4f..ec5378f01ce3 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -41,6 +41,7 @@ cc_library_static { "com_android_server_companion_virtual_InputController.cpp", "com_android_server_devicepolicy_CryptoTestHelper.cpp", "com_android_server_display_DisplayControl.cpp", + "com_android_server_display_SmallAreaDetectionController.cpp", "com_android_server_connectivity_Vpn.cpp", "com_android_server_gpu_GpuService.cpp", "com_android_server_HardwarePropertiesManagerService.cpp", diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp new file mode 100644 index 000000000000..b256f168f2af --- /dev/null +++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 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. + */ + +#define LOG_TAG "SmallAreaDetectionController" + +#include <gui/SurfaceComposerClient.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedPrimitiveArray.h> + +#include "jni.h" +#include "utils/Log.h" + +namespace android { +static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids, + jfloatArray jthresholds) { + if (juids == nullptr || jthresholds == nullptr) return; + + ScopedIntArrayRO uids(env, juids); + ScopedFloatArrayRO thresholds(env, jthresholds); + + if (uids.size() != thresholds.size()) { + ALOGE("uids size exceeds thresholds size!"); + return; + } + + std::vector<int32_t> uidVector; + std::vector<float> thresholdVector; + size_t size = uids.size(); + uidVector.reserve(size); + thresholdVector.reserve(size); + for (int i = 0; i < size; i++) { + uidVector.push_back(static_cast<int32_t>(uids[i])); + thresholdVector.push_back(static_cast<float>(thresholds[i])); + } + SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector); +} + +static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid, + jfloat threshold) { + SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold); +} + +static const JNINativeMethod gMethods[] = { + {"nativeUpdateSmallAreaDetection", "([I[F)V", (void*)nativeUpdateSmallAreaDetection}, + {"nativeSetSmallAreaDetectionThreshold", "(IF)V", + (void*)nativeSetSmallAreaDetectionThreshold}, +}; + +int register_android_server_display_smallAreaDetectionController(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/server/display/SmallAreaDetectionController", + gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 97d7be6a718e..f6f673746609 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -67,6 +67,7 @@ int register_android_server_app_GameManagerService(JNIEnv* env); int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env); int register_com_android_server_display_DisplayControl(JNIEnv* env); int register_com_android_server_SystemClockTime(JNIEnv* env); +int register_android_server_display_smallAreaDetectionController(JNIEnv* env); }; using namespace android; @@ -126,5 +127,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_com_android_server_wm_TaskFpsCallbackController(env); register_com_android_server_display_DisplayControl(env); register_com_android_server_SystemClockTime(env); + register_android_server_display_smallAreaDetectionController(env); return JNI_VERSION_1_4; } diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp index fb14419a13c0..e28028f9fc2b 100644 --- a/services/tests/displayservicetests/Android.bp +++ b/services/tests/displayservicetests/Android.bp @@ -35,6 +35,7 @@ android_test { "mockingservicestests-utils-mockito", "platform-compat-test-rules", "platform-test-annotations", + "service-permission.stubs.system_server", "services.core", "servicestests-utils", "testables", diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 40d7a774608b..a23539e37409 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -64,6 +64,7 @@ import android.compat.testing.PlatformCompatChangeRule; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Rect; @@ -113,6 +114,7 @@ import com.android.server.display.DisplayManagerService.SyncRoot; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.input.InputManagerInternal; import com.android.server.lights.LightsManager; +import com.android.server.pm.UserManagerInternal; import com.android.server.sensors.SensorManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -291,10 +293,11 @@ public class DisplayManagerServiceTest { @Mock LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy; @Mock IBinder mMockDisplayToken; @Mock SensorManagerInternal mMockSensorManagerInternal; - @Mock SensorManager mSensorManager; - @Mock DisplayDeviceConfig mMockDisplayDeviceConfig; + @Mock PackageManagerInternal mMockPackageManagerInternal; + @Mock UserManagerInternal mMockUserManagerInternal; + @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor; @Mock DisplayManagerFlags mMockFlags; @@ -315,6 +318,10 @@ public class DisplayManagerServiceTest { LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); LocalServices.addService( VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal); // TODO: b/287945043 mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mResources = Mockito.spy(mContext.getResources()); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java new file mode 100644 index 000000000000..1ce79a5b596b --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2023 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.display; + +import static android.os.Process.INVALID_UID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContextWrapper; +import android.content.pm.PackageManagerInternal; +import android.provider.DeviceConfigInterface; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SmallAreaDetectionControllerTest { + + @Rule + public MockitoRule mRule = MockitoJUnit.rule(); + + @Mock + private PackageManagerInternal mMockPackageManagerInternal; + @Mock + private UserManagerInternal mMockUserManagerInternal; + + private SmallAreaDetectionController mSmallAreaDetectionController; + + private static final String PKG_A = "com.a.b.c"; + private static final String PKG_B = "com.d.e.f"; + private static final String PKG_NOT_INSTALLED = "com.not.installed"; + private static final float THRESHOLD_A = 0.05f; + private static final float THRESHOLD_B = 0.07f; + private static final int USER_1 = 110; + private static final int USER_2 = 111; + private static final int UID_A_1 = 11011111; + private static final int UID_A_2 = 11111111; + private static final int UID_B_1 = 11022222; + private static final int UID_B_2 = 11122222; + + @Before + public void setup() { + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal); + + when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2}); + when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1); + when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2); + when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1); + when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2); + when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn( + INVALID_UID); + when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn( + INVALID_UID); + + mSmallAreaDetectionController = spy(new SmallAreaDetectionController( + new ContextWrapper(ApplicationProvider.getApplicationContext()), + DeviceConfigInterface.REAL)); + doNothing().when(mSmallAreaDetectionController).updateSmallAreaDetection(any(), any()); + } + + @Test + public void testUpdateAllowlist_validProperty() { + final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B; + mSmallAreaDetectionController.updateAllowlist(property); + + final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2}; + final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + eq(resultThresholdArray)); + } + + @Test + public void testUpdateAllowlist_includeInvalidRow() { + final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B; + mSmallAreaDetectionController.updateAllowlist(property); + + final int[] resultUidArray = {UID_B_1, UID_B_2}; + final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + eq(resultThresholdArray)); + } + + @Test + public void testUpdateAllowlist_includeNotInstalledPkg() { + final String property = + PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B; + mSmallAreaDetectionController.updateAllowlist(property); + + final int[] resultUidArray = {UID_A_1, UID_A_2}; + final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + eq(resultThresholdArray)); + } + + @Test + public void testUpdateAllowlist_invalidProperty() { + final String property = PKG_A; + mSmallAreaDetectionController.updateAllowlist(property); + + verify(mSmallAreaDetectionController, never()).updateSmallAreaDetection(any(), any()); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java index c7e1bda2e1b8..e58a2342a718 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java @@ -16,41 +16,51 @@ package com.android.server.pm; +import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import static android.content.pm.PackageManager.DELETE_KEEP_DATA; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.AppOpsManager; import android.content.Context; +import android.content.Intent; import android.content.IntentSender; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; +import android.content.pm.PackageArchiver; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.os.Binder; import android.os.Build; +import android.os.Bundle; import android.os.ParcelableException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.text.TextUtils; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.pm.pkg.ArchiveState; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageUserStateImpl; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -64,7 +74,8 @@ import java.util.List; public class PackageArchiverServiceTest { private static final String PACKAGE = "com.example"; - private static final String CALLER_PACKAGE = "com.vending"; + private static final String CALLER_PACKAGE = "com.caller"; + private static final String INSTALLER_PACKAGE = "com.installer"; @Rule public final MockSystemRule mMockSystem = new MockSystemRule(); @@ -84,11 +95,11 @@ public class PackageArchiverServiceTest { private final InstallSource mInstallSource = InstallSource.create( - CALLER_PACKAGE, - CALLER_PACKAGE, - CALLER_PACKAGE, + INSTALLER_PACKAGE, + INSTALLER_PACKAGE, + INSTALLER_PACKAGE, Binder.getCallingUid(), - CALLER_PACKAGE, + INSTALLER_PACKAGE, /* installerAttributionTag= */ null, /* packageSource= */ 0); @@ -96,6 +107,8 @@ public class PackageArchiverServiceTest { private final int mUserId = UserHandle.CURRENT.getIdentifier(); + private PackageUserStateImpl mUserState; + private PackageSetting mPackageSetting; private PackageArchiverService mArchiveService; @@ -116,11 +129,16 @@ public class PackageArchiverServiceTest { when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn( mPackageState); + when(mComputer.getPackageStateFiltered(eq(INSTALLER_PACKAGE), anyInt(), + anyInt())).thenReturn(mock(PackageStateInternal.class)); when(mPackageState.getPackageName()).thenReturn(PACKAGE); when(mPackageState.getInstallSource()).thenReturn(mInstallSource); mPackageSetting = createBasicPackageSetting(); when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn( mPackageSetting); + mUserState = new PackageUserStateImpl().setInstalled(true); + mPackageSetting.setUserState(mUserId, mUserState); + when(mPackageState.getUserStateOrDefault(eq(mUserId))).thenReturn(mUserState); when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps); when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn( mLauncherActivityInfos); @@ -135,9 +153,7 @@ public class PackageArchiverServiceTest { Exception e = assertThrows( SecurityException.class, () -> mArchiveService.requestArchive(PACKAGE, "different", mIntentSender, - UserHandle.CURRENT - ) - ); + UserHandle.CURRENT)); assertThat(e).hasMessageThat().isEqualTo( String.format( "The UID %s of callerPackageName set by the caller doesn't match the " @@ -154,9 +170,7 @@ public class PackageArchiverServiceTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, - UserHandle.CURRENT - ) - ); + UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("Package %s not found.", PACKAGE)); @@ -169,9 +183,7 @@ public class PackageArchiverServiceTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, - UserHandle.CURRENT - ) - ); + UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("Package %s not found.", PACKAGE)); @@ -193,25 +205,28 @@ public class PackageArchiverServiceTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, - UserHandle.CURRENT - ) - ); + UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("No installer found to archive app %s.", PACKAGE)); } @Test - public void archiveApp_success() { - List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>(); - for (LauncherActivityInfo mainActivity : createLauncherActivities()) { - // TODO(b/278553670) Extract and store launcher icons - ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo( - mainActivity.getLabel().toString(), - Path.of("/TODO"), null); - activityInfos.add(activityInfo); - } + public void archiveApp_noMainActivities() { + when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn( + List.of()); + Exception e = assertThrows( + ParcelableException.class, + () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, + UserHandle.CURRENT)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + TextUtils.formatSimple("The app %s does not have a main activity.", PACKAGE)); + } + + @Test + public void archiveApp_success() { mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT); verify(mInstallerService).uninstall( @@ -220,7 +235,112 @@ public class PackageArchiverServiceTest { eq(UserHandle.CURRENT.getIdentifier())); assertThat(mPackageSetting.readUserState( UserHandle.CURRENT.getIdentifier()).getArchiveState()).isEqualTo( - new ArchiveState(activityInfos, CALLER_PACKAGE)); + createArchiveState()); + } + + @Test + public void unarchiveApp_callerPackageNameIncorrect() { + mUserState.setArchiveState(createArchiveState()).setInstalled(false); + + Exception e = assertThrows( + SecurityException.class, + () -> mArchiveService.requestUnarchive(PACKAGE, "different", + UserHandle.CURRENT)); + assertThat(e).hasMessageThat().isEqualTo( + String.format( + "The UID %s of callerPackageName set by the caller doesn't match the " + + "caller's actual UID %s.", + 0, + Binder.getCallingUid())); + } + + @Test + public void unarchiveApp_packageNotInstalled() { + mUserState.setArchiveState(createArchiveState()).setInstalled(false); + when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn( + null); + + Exception e = assertThrows( + ParcelableException.class, + () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE, + UserHandle.CURRENT)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + String.format("Package %s not found.", PACKAGE)); + } + + @Test + public void unarchiveApp_notArchived() { + Exception e = assertThrows( + ParcelableException.class, + () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE, + UserHandle.CURRENT)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + String.format("Package %s is not currently archived.", PACKAGE)); + } + + @Test + public void unarchiveApp_noInstallerFound() { + mUserState.setArchiveState(createArchiveState()); + InstallSource otherInstallSource = + InstallSource.create( + CALLER_PACKAGE, + CALLER_PACKAGE, + /* installerPackageName= */ null, + Binder.getCallingUid(), + /* updateOwnerPackageName= */ null, + /* installerAttributionTag= */ null, + /* packageSource= */ 0); + when(mPackageState.getInstallSource()).thenReturn(otherInstallSource); + + Exception e = assertThrows( + ParcelableException.class, + () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE, + UserHandle.CURRENT)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + String.format("No installer found to unarchive app %s.", PACKAGE)); + } + + @Test + public void unarchiveApp_success() { + mUserState.setArchiveState(createArchiveState()).setInstalled(false); + + mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT); + mMockSystem.mocks().getHandler().flush(); + + ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mContext).sendOrderedBroadcastAsUser( + intentCaptor.capture(), + eq(UserHandle.CURRENT), + /* receiverPermission = */ isNull(), + eq(AppOpsManager.OP_NONE), + any(Bundle.class), + /* resultReceiver= */ isNull(), + /* scheduler= */ isNull(), + /* initialCode= */ eq(0), + /* initialData= */ isNull(), + /* initialExtras= */ isNull()); + Intent intent = intentCaptor.getValue(); + assertThat(intent.getFlags() & FLAG_RECEIVER_FOREGROUND).isNotEqualTo(0); + assertThat(intent.getStringExtra(PackageArchiver.EXTRA_UNARCHIVE_PACKAGE_NAME)).isEqualTo( + PACKAGE); + assertThat( + intent.getBooleanExtra(PackageArchiver.EXTRA_UNARCHIVE_ALL_USERS, true)).isFalse(); + assertThat(intent.getPackage()).isEqualTo(INSTALLER_PACKAGE); + } + + private static ArchiveState createArchiveState() { + List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>(); + for (LauncherActivityInfo mainActivity : createLauncherActivities()) { + // TODO(b/278553670) Extract and store launcher icons + ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo( + mainActivity.getLabel().toString(), + Path.of("/TODO"), null); + activityInfos.add(activityInfo); + } + return new ArchiveState(activityInfos, INSTALLER_PACKAGE); } private static List<LauncherActivityInfo> createLauncherActivities() { diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java index 10b49c67e8bb..bc826a3cf4a6 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java @@ -30,7 +30,6 @@ import static org.mockito.Mockito.when; import android.content.res.Resources; import android.os.VibrationEffect; -import android.os.Vibrator; import android.os.VibratorInfo; import android.util.AtomicFile; import android.util.SparseArray; @@ -73,12 +72,10 @@ public class HapticFeedbackCustomizationTest { VibrationEffect.createWaveform(new long[] {123}, new int[] {254}, -1); @Mock private Resources mResourcesMock; - @Mock private Vibrator mVibratorMock; @Mock private VibratorInfo mVibratorInfoMock; @Before public void setUp() { - when(mVibratorMock.getInfo()).thenReturn(mVibratorInfoMock); when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true); } @@ -220,17 +217,17 @@ public class HapticFeedbackCustomizationTest { public void testParseCustomizations_noCustomizationFile_returnsNull() throws Exception { setCustomizationFilePath(""); - assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock)) + assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)) .isNull(); setCustomizationFilePath(null); - assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock)) + assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)) .isNull(); setCustomizationFilePath("non_existent_file.xml"); - assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock)) + assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)) .isNull(); } @@ -387,7 +384,7 @@ public class HapticFeedbackCustomizationTest { String xml, SparseArray<VibrationEffect> expectedCustomizations) throws Exception { setupCustomizationFile(xml); assertThat(expectedCustomizations.contentEquals( - HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock))) + HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))) .isTrue(); } @@ -395,13 +392,15 @@ public class HapticFeedbackCustomizationTest { setupCustomizationFile(xml); assertThrows("Expected haptic feedback customization to fail for " + xml, CustomizationParserException.class, - () -> HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock)); + () -> HapticFeedbackCustomization.loadVibrations( + mResourcesMock, mVibratorInfoMock)); } private void assertParseCustomizationsFails() throws Exception { assertThrows("Expected haptic feedback customization to fail", CustomizationParserException.class, - () -> HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock)); + () -> HapticFeedbackCustomization.loadVibrations( + mResourcesMock, mVibratorInfoMock)); } private void setupCustomizationFile(String xml) throws Exception { 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 cae811e9bb35..a91bd2b55f76 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java @@ -31,8 +31,10 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; +import android.hardware.vibrator.IVibrator; +import android.os.VibrationAttributes; import android.os.VibrationEffect; -import android.os.test.FakeVibrator; +import android.os.VibratorInfo; import android.util.AtomicFile; import android.util.SparseArray; @@ -58,7 +60,7 @@ public class HapticFeedbackVibrationProviderTest { VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK, 0.3497f).compose(); private Context mContext = InstrumentationRegistry.getContext(); - private FakeVibrator mVibrator = new FakeVibrator(mContext); + private VibratorInfo mVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO; @Mock private Resources mResourcesMock; @@ -66,14 +68,14 @@ public class HapticFeedbackVibrationProviderTest { public void testNonExistentCustomization_useDefault() throws Exception { // No customization file is set. HapticFeedbackVibrationProvider hapticProvider = - new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator); + new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo); assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK)) .isEqualTo(VibrationEffect.get(EFFECT_TICK)); // The customization file specifies no customization. setupCustomizationFile("<haptic-feedback-constants></haptic-feedback-constants>"); - hapticProvider = new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator); + hapticProvider = new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo); assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK)) .isEqualTo(VibrationEffect.get(EFFECT_TICK)); @@ -83,7 +85,7 @@ public class HapticFeedbackVibrationProviderTest { public void testExceptionParsingCustomizations_useDefault() throws Exception { setupCustomizationFile("<bad-xml></bad-xml>"); HapticFeedbackVibrationProvider hapticProvider = - new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator); + new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo); assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK)) .isEqualTo(VibrationEffect.get(EFFECT_TICK)); @@ -96,7 +98,7 @@ public class HapticFeedbackVibrationProviderTest { customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT); HapticFeedbackVibrationProvider hapticProvider = - new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations); + new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations); // The override for `CONTEXT_CLICK` is used. assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK)) @@ -109,11 +111,15 @@ public class HapticFeedbackVibrationProviderTest { @Test public void testDoNotUseInvalidCustomizedVibration() throws Exception { mockVibratorPrimitiveSupport(new int[] {}); - SparseArray<VibrationEffect> customizations = new SparseArray<>(); - customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT); + String xml = "<haptic-feedback-constants>" + + "<constant id=\"" + CONTEXT_CLICK + "\">" + + PRIMITIVE_CLICK_EFFECT + + "</constant>" + + "</haptic-feedback-constants>"; + setupCustomizationFile(xml); HapticFeedbackVibrationProvider hapticProvider = - new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations); + new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo); // The override for `CONTEXT_CLICK` is not used because the vibration is not supported. assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK)) @@ -132,14 +138,14 @@ public class HapticFeedbackVibrationProviderTest { // Test with a customization available for `TEXT_HANDLE_MOVE`. HapticFeedbackVibrationProvider hapticProvider = - new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations); + new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations); assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull(); // Test with no customization available for `TEXT_HANDLE_MOVE`. hapticProvider = new HapticFeedbackVibrationProvider( - mResourcesMock, mVibrator, /* hapticCustomizations= */ null); + mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null); assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull(); } @@ -153,7 +159,7 @@ public class HapticFeedbackVibrationProviderTest { // Test with a customization available for `TEXT_HANDLE_MOVE`. HapticFeedbackVibrationProvider hapticProvider = - new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations); + new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations); assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)) .isEqualTo(PRIMITIVE_CLICK_EFFECT); @@ -161,7 +167,7 @@ public class HapticFeedbackVibrationProviderTest { // Test with no customization available for `TEXT_HANDLE_MOVE`. hapticProvider = new HapticFeedbackVibrationProvider( - mResourcesMock, mVibrator, /* hapticCustomizations= */ null); + mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null); assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)) .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK)); @@ -176,14 +182,14 @@ public class HapticFeedbackVibrationProviderTest { customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT); HapticFeedbackVibrationProvider hapticProvider = - new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations); + new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations); assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)) .isEqualTo(PRIMITIVE_CLICK_EFFECT); mockSafeModeEnabledVibration(null); hapticProvider = - new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations); + new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations); assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)) .isEqualTo(PRIMITIVE_CLICK_EFFECT); @@ -193,20 +199,9 @@ public class HapticFeedbackVibrationProviderTest { public void testNoValidCustomizationPresentForSafeModeEnabled_resourceBasedVibrationUsed() throws Exception { mockSafeModeEnabledVibration(10, 20, 30, 40); - SparseArray<VibrationEffect> customizations = new SparseArray<>(); - customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT); - - // Test with a customization that is not supported by the vibrator. HapticFeedbackVibrationProvider hapticProvider = - new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations); - - assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)) - .isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1)); - - // Test with no customizations. - hapticProvider = new HapticFeedbackVibrationProvider( - mResourcesMock, mVibrator, /* hapticCustomizations= */ null); + mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null); assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)) .isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1)); @@ -216,25 +211,44 @@ public class HapticFeedbackVibrationProviderTest { public void testNoValidCustomizationAndResourcePresentForSafeModeEnabled_noVibrationUsed() throws Exception { mockSafeModeEnabledVibration(null); - SparseArray<VibrationEffect> customizations = new SparseArray<>(); - customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT); - - // Test with a customization that is not supported by the vibrator. HapticFeedbackVibrationProvider hapticProvider = - new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations); + new HapticFeedbackVibrationProvider( + mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null); assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull(); + } - // Test with no customizations. - hapticProvider = - new HapticFeedbackVibrationProvider( - mResourcesMock, mVibrator, /* hapticCustomizations= */ null); + @Test + public void testVibrationAttribute_forNotBypassingIntensitySettings() { + HapticFeedbackVibrationProvider hapticProvider = + new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo); - assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull(); + VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( + SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ false); + + assertThat(attrs.getFlags() & VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) + .isEqualTo(0); + } + + @Test + public void testVibrationAttribute_forByassingIntensitySettings() { + HapticFeedbackVibrationProvider hapticProvider = + new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo); + + VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( + SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ true); + + assertThat(attrs.getFlags() & VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) + .isNotEqualTo(0); } private void mockVibratorPrimitiveSupport(int... supportedPrimitives) { - mVibrator = new FakeVibrator(mContext, supportedPrimitives); + VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); + for (int primitive : supportedPrimitives) { + builder.setSupportedPrimitive(primitive, 10); + } + mVibratorInfo = builder.build(); } private void mockHapticTextSupport(boolean supported) { 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 4e3a893954a2..c25f0cb91caa 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -30,6 +31,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -45,6 +47,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManagerInternal; +import android.content.res.Resources; import android.hardware.input.IInputManager; import android.hardware.input.InputManager; import android.hardware.input.InputManagerGlobal; @@ -79,8 +82,10 @@ import android.os.vibrator.VibrationConfig; import android.os.vibrator.VibrationEffectSegment; import android.provider.Settings; import android.util.ArraySet; +import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.Display; +import android.view.HapticFeedbackConstants; import android.view.InputDevice; import androidx.test.InstrumentationRegistry; @@ -169,6 +174,8 @@ public class VibratorManagerServiceTest { private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>(); + private SparseArray<VibrationEffect> mHapticFeedbackVibrationMap = new SparseArray<>(); + private VibratorManagerService mService; private Context mContextSpy; private TestLooper mTestLooper; @@ -309,6 +316,12 @@ public class VibratorManagerServiceTest { mExternalVibratorService = (VibratorManagerService.ExternalVibratorService) serviceInstance; } + + HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider( + Resources resources, VibratorInfo vibratorInfo) { + return new HapticFeedbackVibrationProvider( + resources, vibratorInfo, mHapticFeedbackVibrationMap); + } }); return mService; } @@ -623,6 +636,18 @@ public class VibratorManagerServiceTest { } @Test + public void vibrate_withoutVibratePermission_throwsSecurityException() { + denyPermission(android.Manifest.permission.VIBRATE); + VibratorManagerService service = createSystemReadyService(); + + assertThrows("Expected vibrating without permission to fail!", + SecurityException.class, + () -> vibrate(service, + VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK), + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH))); + } + + @Test public void vibrate_withRingtone_usesRingerModeSettings() throws Exception { mockVibrators(1); FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); @@ -1274,6 +1299,60 @@ public class VibratorManagerServiceTest { } @Test + public void performHapticFeedback_doesNotRequirePermission() throws Exception { + denyPermission(android.Manifest.permission.VIBRATE); + mHapticFeedbackVibrationMap.put( + HapticFeedbackConstants.KEYBOARD_TAP, + VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); + mockVibrators(1); + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); + fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); + VibratorManagerService service = createSystemReadyService(); + + HalVibration vibration = + performHapticFeedbackAndWaitUntilFinished( + service, HapticFeedbackConstants.KEYBOARD_TAP, /* always= */ true); + + List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments(); + assertEquals(1, playedSegments.size()); + PrebakedSegment segment = (PrebakedSegment) playedSegments.get(0); + assertEquals(VibrationEffect.EFFECT_CLICK, segment.getEffectId()); + assertEquals(VibrationAttributes.USAGE_TOUCH, vibration.callerInfo.attrs.getUsage()); + } + + @Test + public void performHapticFeedback_doesNotVibrateWhenVibratorInfoNotReady() throws Exception { + denyPermission(android.Manifest.permission.VIBRATE); + mHapticFeedbackVibrationMap.put( + HapticFeedbackConstants.KEYBOARD_TAP, + VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); + mockVibrators(1); + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); + fakeVibrator.setVibratorInfoLoadSuccessful(false); + fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); + VibratorManagerService service = createService(); + + performHapticFeedbackAndWaitUntilFinished( + service, HapticFeedbackConstants.KEYBOARD_TAP, /* always= */ true); + + assertTrue(fakeVibrator.getAllEffectSegments().isEmpty()); + } + + @Test + public void performHapticFeedback_doesNotVibrateForInvalidConstant() throws Exception { + denyPermission(android.Manifest.permission.VIBRATE); + mockVibrators(1); + VibratorManagerService service = createSystemReadyService(); + + // These are bad haptic feedback IDs, so expect no vibration played. + performHapticFeedbackAndWaitUntilFinished(service, /* constant= */ -1, /* always= */ false); + performHapticFeedbackAndWaitUntilFinished( + service, HapticFeedbackConstants.NO_HAPTICS, /* always= */ true); + + assertTrue(mVibratorProviders.get(1).getAllEffectSegments().isEmpty()); + } + + @Test public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception { int defaultNotificationIntensity = mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION); @@ -2231,6 +2310,18 @@ public class VibratorManagerServiceTest { mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); } + private HalVibration performHapticFeedbackAndWaitUntilFinished(VibratorManagerService service, + int constant, boolean always) throws InterruptedException { + HalVibration vib = + service.performHapticFeedbackInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, + constant, always, "some reason", service); + if (vib != null) { + vib.waitForEnd(); + } + + return vib; + } + private void vibrateAndWaitUntilFinished(VibratorManagerService service, VibrationEffect effect, VibrationAttributes attrs) throws InterruptedException { vibrateAndWaitUntilFinished(service, CombinedVibration.createParallel(effect), attrs); @@ -2239,8 +2330,8 @@ public class VibratorManagerServiceTest { private void vibrateAndWaitUntilFinished(VibratorManagerService service, CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException { HalVibration vib = - service.vibrateInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, effect, attrs, - "some reason", service); + service.vibrateWithPermissionCheck(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, + effect, attrs, "some reason", service); if (vib != null) { vib.waitForEnd(); } @@ -2271,4 +2362,9 @@ public class VibratorManagerServiceTest { } return predicateResult; } + + private void denyPermission(String permission) { + doThrow(new SecurityException()).when(mContextSpy) + .enforceCallingOrSelfPermission(eq(permission), anyString()); + } } diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java index 8fadecd0dc38..e13dc3eea729 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java @@ -65,6 +65,12 @@ public class ShortcutLoggingTests extends ShortcutKeyTestBase { KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, ALT_ON}, {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, KeyboardLogEvent.BACK, KeyEvent.KEYCODE_BACK, 0}, + {"Meta + `(grave) -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_GRAVE}, + KeyboardLogEvent.BACK, KeyEvent.KEYCODE_GRAVE, META_ON}, + {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT}, + KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DPAD_LEFT, META_ON}, + {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL}, + KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DEL, META_ON}, {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH}, KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_APP_SWITCH, 0}, {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST}, diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index dd90e0450280..bf86563e3d86 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR; import static android.view.DisplayCutout.NO_CUTOUT; import static android.view.InsetsSource.ID_IME; import static android.view.RoundedCorners.NO_ROUNDED_CORNERS; @@ -427,11 +428,11 @@ public class DisplayPolicyTests extends WindowTestsBase { @SetupWindows(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD }) @Test public void testImeMinimalSourceFrame() { + Assume.assumeFalse("Behavior no longer needed with ENABLE_HIDE_IME_CAPTION_BAR", + ENABLE_HIDE_IME_CAPTION_BAR); + final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); - final DisplayInfo displayInfo = new DisplayInfo(); - displayInfo.logicalWidth = 1000; - displayInfo.logicalHeight = 2000; - displayInfo.rotation = ROTATION_0; + final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo(); WindowManager.LayoutParams attrs = mNavBarWindow.mAttrs; displayPolicy.addWindowLw(mNavBarWindow, attrs); @@ -466,10 +467,6 @@ public class DisplayPolicyTests extends WindowTestsBase { @Test public void testImeInsetsGivenContentFrame() { final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); - final DisplayInfo displayInfo = new DisplayInfo(); - displayInfo.logicalWidth = 1000; - displayInfo.logicalHeight = 2000; - displayInfo.rotation = ROTATION_0; mDisplayContent.setInputMethodWindowLocked(mImeWindow); mImeWindow.getControllableInsetProvider().setServerVisible(true); diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java index 06033c7ebf75..3fcec963593c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java @@ -184,7 +184,7 @@ public class LetterboxConfigurationPersisterTest { LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); firstPersister.setLetterboxPositionForVerticalReachability(false, LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP); - waitForCompletion(mPersisterQueue); + waitForCompletion(firstPersisterQueue); final int newPositionForHorizontalReachability = firstPersister.getLetterboxPositionForHorizontalReachability(false); final int newPositionForVerticalReachability = diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp index 2ccc0fa9e1e7..a2ae56eaeadc 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -95,6 +95,7 @@ java_defaults { "flickertestapplib", "flickerlib", "flickerlib-helpers", + "flickerlib-trace_processor_shell", "platform-test-annotations", "wm-flicker-common-app-helpers", "wm-shell-flicker-utils", diff --git a/tests/FlickerTests/manifests/AndroidManifest.xml b/tests/FlickerTests/manifests/AndroidManifest.xml index 1a34d9ea0f83..6bc7cbe88589 100644 --- a/tests/FlickerTests/manifests/AndroidManifest.xml +++ b/tests/FlickerTests/manifests/AndroidManifest.xml @@ -44,8 +44,12 @@ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" /> <!-- ActivityOptions.makeCustomTaskAnimation() --> <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" /> - <!-- Allow the test to write directly to /sdcard/ --> - <application android:requestLegacyExternalStorage="true" android:largeHeap="true"> + <!-- Allow the test to connect to perfetto trace processor --> + <uses-permission android:name="android.permission.INTERNET"/> + <!-- Allow the test to write directly to /sdcard/ and connect to trace processor --> + <application android:requestLegacyExternalStorage="true" + android:networkSecurityConfig="@xml/network_security_config" + android:largeHeap="true"> <uses-library android:name="android.test.runner"/> <uses-library android:name="androidx.window.extensions" android:required="false"/> diff --git a/tests/FlickerTests/res/xml/network_security_config.xml b/tests/FlickerTests/res/xml/network_security_config.xml new file mode 100644 index 000000000000..4bd9ca049f55 --- /dev/null +++ b/tests/FlickerTests/res/xml/network_security_config.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<network-security-config> + <domain-config cleartextTrafficPermitted="true"> + <domain includeSubdomains="true">localhost</domain> + </domain-config> +</network-security-config> diff --git a/tests/SurfaceViewBufferTests/Android.bp b/tests/SurfaceViewBufferTests/Android.bp index dc75f00e7cdc..38313f85c31d 100644 --- a/tests/SurfaceViewBufferTests/Android.bp +++ b/tests/SurfaceViewBufferTests/Android.bp @@ -23,7 +23,10 @@ package { android_test { name: "SurfaceViewBufferTests", - srcs: ["**/*.java","**/*.kt"], + srcs: [ + "**/*.java", + "**/*.kt", + ], manifest: "AndroidManifest.xml", test_config: "AndroidTest.xml", platform_apis: true, @@ -41,6 +44,7 @@ android_test { "kotlin-stdlib", "kotlinx-coroutines-android", "flickerlib", + "flickerlib-trace_processor_shell", "truth-prebuilt", "cts-wm-util", "CtsSurfaceValidatorLib", @@ -60,7 +64,7 @@ cc_library_shared { "libandroid", ], include_dirs: [ - "system/core/include" + "system/core/include", ], stl: "libc++_static", cflags: [ diff --git a/tests/SurfaceViewBufferTests/AndroidManifest.xml b/tests/SurfaceViewBufferTests/AndroidManifest.xml index 78415e8641eb..798c67a320ce 100644 --- a/tests/SurfaceViewBufferTests/AndroidManifest.xml +++ b/tests/SurfaceViewBufferTests/AndroidManifest.xml @@ -29,9 +29,12 @@ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <!-- Save failed test bitmap images !--> <uses-permission android:name="android.Manifest.permission.WRITE_EXTERNAL_STORAGE"/> + <!-- Allow the test to connect to perfetto trace processor --> + <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="false" - android:supportsRtl="true"> + android:supportsRtl="true" + android:networkSecurityConfig="@xml/network_security_config"> <activity android:name=".MainActivity" android:taskAffinity="com.android.test.MainActivity" android:theme="@style/AppTheme" diff --git a/tests/SurfaceViewBufferTests/res/xml/network_security_config.xml b/tests/SurfaceViewBufferTests/res/xml/network_security_config.xml new file mode 100644 index 000000000000..4bd9ca049f55 --- /dev/null +++ b/tests/SurfaceViewBufferTests/res/xml/network_security_config.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<network-security-config> + <domain-config cleartextTrafficPermitted="true"> + <domain includeSubdomains="true">localhost</domain> + </domain-config> +</network-security-config> diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt index a38019d67376..b03b7335b08b 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt @@ -21,11 +21,9 @@ import android.graphics.Color import android.graphics.Rect import android.util.Log import androidx.test.ext.junit.rules.ActivityScenarioRule -import android.tools.common.flicker.subject.layers.LayerSubject import android.tools.common.traces.surfaceflinger.LayersTrace -import android.tools.device.traces.io.ResultWriter -import android.tools.device.traces.monitors.surfaceflinger.LayersTraceMonitor import android.tools.device.traces.monitors.withSFTracing +import android.tools.device.traces.monitors.PerfettoTraceMonitor import junit.framework.Assert import org.junit.After import org.junit.Before @@ -33,6 +31,7 @@ import org.junit.Rule import java.io.FileOutputStream import java.io.IOException import java.util.concurrent.CountDownLatch +import perfetto.protos.PerfettoConfig.SurfaceFlingerLayersConfig open class SurfaceTracingTestBase(useBlastAdapter: Boolean) : SurfaceViewBufferTestBase(useBlastAdapter) { @@ -43,7 +42,7 @@ open class SurfaceTracingTestBase(useBlastAdapter: Boolean) : @Before override fun setup() { super.setup() - stopLayerTrace() + PerfettoTraceMonitor.stopAllSessions() addSurfaceView() } @@ -83,10 +82,6 @@ open class SurfaceTracingTestBase(useBlastAdapter: Boolean) : instrumentation.waitForIdleSync() } - private fun stopLayerTrace() { - LayersTraceMonitor().stop(ResultWriter()) - } - fun checkPixels(bounds: Rect, @ColorInt color: Int) { val screenshot = instrumentation.getUiAutomation().takeScreenshot() val pixels = IntArray(screenshot.width * screenshot.height) @@ -106,14 +101,19 @@ open class SurfaceTracingTestBase(useBlastAdapter: Boolean) : Log.e("SurfaceViewBufferTests", "Error writing bitmap to file", e) } } - Assert.assertEquals("Checking $bounds found mismatch $i,$j", - Color.valueOf(color), Color.valueOf(actualColor)) + Assert.assertEquals( + "Checking $bounds found mismatch $i,$j", + Color.valueOf(color), + Color.valueOf(actualColor) + ) } } } private companion object { - private const val TRACE_FLAGS = - (1 shl 0) or (1 shl 5) or (1 shl 6) // TRACE_CRITICAL | TRACE_BUFFERS | TRACE_SYNC + private val TRACE_FLAGS = listOf( + SurfaceFlingerLayersConfig.TraceFlag.TRACE_FLAG_BUFFERS, + SurfaceFlingerLayersConfig.TraceFlag.TRACE_FLAG_VIRTUAL_DISPLAYS, + ) } }
\ No newline at end of file diff --git a/tests/TaskOrganizerTest/Android.bp b/tests/TaskOrganizerTest/Android.bp index 9b72d359aae6..bf12f423f145 100644 --- a/tests/TaskOrganizerTest/Android.bp +++ b/tests/TaskOrganizerTest/Android.bp @@ -25,7 +25,10 @@ package { android_test { name: "TaskOrganizerTest", - srcs: ["**/*.java","**/*.kt"], + srcs: [ + "**/*.java", + "**/*.kt", + ], manifest: "AndroidManifest.xml", test_config: "AndroidTest.xml", platform_apis: true, @@ -39,6 +42,7 @@ android_test { "kotlin-stdlib", "kotlinx-coroutines-android", "flickerlib", + "flickerlib-trace_processor_shell", "truth-prebuilt", ], -}
\ No newline at end of file +} diff --git a/tests/TaskOrganizerTest/AndroidManifest.xml b/tests/TaskOrganizerTest/AndroidManifest.xml index 1f1bd3ef7d81..cbeb246eb86c 100644 --- a/tests/TaskOrganizerTest/AndroidManifest.xml +++ b/tests/TaskOrganizerTest/AndroidManifest.xml @@ -16,9 +16,11 @@ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/> + <!-- Allow the test to connect to perfetto trace processor --> + <uses-permission android:name="android.permission.INTERNET"/> <!-- Enable / Disable tracing !--> <uses-permission android:name="android.permission.DUMP" /> - <application> + <application android:networkSecurityConfig="@xml/network_security_config"> <activity android:name="TaskOrganizerMultiWindowTest" android:label="TaskOrganizer MW Test" android:exported="true" diff --git a/tests/TaskOrganizerTest/res/xml/network_security_config.xml b/tests/TaskOrganizerTest/res/xml/network_security_config.xml new file mode 100644 index 000000000000..e450a993da28 --- /dev/null +++ b/tests/TaskOrganizerTest/res/xml/network_security_config.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +~ Copyright (C) 2023 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. +--> +<network-security-config> + <domain-config cleartextTrafficPermitted="true"> + <domain includeSubdomains="true">localhost</domain> + </domain-config> +</network-security-config>
\ No newline at end of file diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt index 6f4f7b13af66..2c7905d552f1 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt @@ -22,8 +22,6 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.runner.AndroidJUnit4 import android.tools.common.datatypes.Size import android.tools.common.flicker.subject.layers.LayersTraceSubject -import android.tools.device.traces.io.ResultWriter -import android.tools.device.traces.monitors.surfaceflinger.LayersTraceMonitor import android.tools.device.traces.monitors.withSFTracing import org.junit.After import org.junit.Before @@ -41,16 +39,13 @@ class ResizeTasksSyncTest { @get:Rule var scenarioRule: ActivityScenarioRule<TaskOrganizerMultiWindowTest> = ActivityScenarioRule<TaskOrganizerMultiWindowTest>( - TaskOrganizerMultiWindowTest::class.java) + TaskOrganizerMultiWindowTest::class.java + ) protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() @Before fun setup() { - val monitor = LayersTraceMonitor() - if (monitor.isEnabled) { - monitor.stop(ResultWriter()) - } val firstTaskBounds = Rect(0, 0, 1080, 1000) val secondTaskBounds = Rect(0, 1000, 1080, 2000) @@ -71,7 +66,7 @@ class ResizeTasksSyncTest { val firstBounds = Rect(0, 0, 1080, 800) val secondBounds = Rect(0, 1000, 1080, 1800) - val trace = withSFTracing(TRACE_FLAGS) { + val trace = withSFTracing() { lateinit var resizeReadyLatch: CountDownLatch scenarioRule.getScenario().onActivity { resizeReadyLatch = it.resizeTaskView(firstBounds, secondBounds) @@ -106,7 +101,6 @@ class ResizeTasksSyncTest { } companion object { - private const val TRACE_FLAGS = 0x1 // TRACE_CRITICAL private const val FIRST_ACTIVITY = "Activity1" private const val SECOND_ACTIVITY = "Activity2" } |