diff options
186 files changed, 3752 insertions, 2564 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 13847c20a117..6d74a840525b 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -48,6 +48,7 @@ aconfig_srcjars = [ ":android.credentials.flags-aconfig-java{.generated_srcjars}", ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", ":android.service.voice.flags-aconfig-java{.generated_srcjars}", + ":aconfig_midi_flags_java_lib{.generated_srcjars}", ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", ":com.android.net.flags-aconfig-java{.generated_srcjars}", ] diff --git a/Android.bp b/Android.bp index a507465aa419..0c199a634725 100644 --- a/Android.bp +++ b/Android.bp @@ -227,7 +227,6 @@ java_library { "android.hardware.radio.messaging-V3-java", "android.hardware.radio.modem-V3-java", "android.hardware.radio.network-V3-java", - "android.hardware.radio.satellite-V1-java", "android.hardware.radio.sim-V3-java", "android.hardware.radio.voice-V3-java", "android.hardware.thermal-V1.0-java-constants", diff --git a/Ravenwood.bp b/Ravenwood.bp index 9218cc9bc3f8..da02298b7415 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -59,6 +59,7 @@ java_genrule_host { // Extract the impl jar from "framework-minus-apex.ravenwood-base" for subsequent build rules. java_genrule_host { name: "framework-minus-apex.ravenwood", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-minus-apex.ravenwood-base{ravenwood.jar}", @@ -66,5 +67,4 @@ java_genrule_host { out: [ "framework-minus-apex.ravenwood.jar", ], - visibility: ["//visibility:public"], } diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp index 30b442336148..e1621008cc33 100644 --- a/api/ApiDocs.bp +++ b/api/ApiDocs.bp @@ -124,7 +124,6 @@ droidstubs { "packages/modules/Media/apex/aidl/stable", ], }, - extensions_info_file: ":sdk-extensions-info", } droidstubs { @@ -132,13 +131,7 @@ droidstubs { defaults: ["framework-doc-stubs-sources-default"], args: metalava_framework_docs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ", - api_levels_annotations_enabled: true, - api_levels_annotations_dirs: [ - "sdk-dir", - "api-versions-jars-dir", - ], - api_levels_sdk_type: "system", - extensions_info_file: ":sdk-extensions-info", + api_levels_module: "api_versions_system", } ///////////////////////////////////////////////////////////////////// diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index fa4bc0f98fa5..7e41660cf1a2 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -695,6 +695,7 @@ java_api_library { "api-stubs-docs-non-updatable.api.contribution", ], visibility: ["//visibility:public"], + enable_validation: false, } java_api_library { @@ -710,6 +711,7 @@ java_api_library { "system-api-stubs-docs-non-updatable.api.contribution", ], visibility: ["//visibility:public"], + enable_validation: false, } java_api_library { @@ -727,6 +729,7 @@ java_api_library { "test-api-stubs-docs-non-updatable.api.contribution", ], visibility: ["//visibility:public"], + enable_validation: false, } java_api_library { @@ -742,6 +745,7 @@ java_api_library { "api-stubs-docs-non-updatable.api.contribution", "system-api-stubs-docs-non-updatable.api.contribution", ], + enable_validation: false, } java_api_library { @@ -761,6 +765,7 @@ java_api_library { "module-lib-api-stubs-docs-non-updatable.api.contribution", ], visibility: ["//visibility:public"], + enable_validation: false, } java_api_library { @@ -774,6 +779,7 @@ java_api_library { "stub-annotations", ], visibility: ["//visibility:public"], + enable_validation: false, } java_api_library { @@ -798,6 +804,7 @@ java_api_library { visibility: [ "//visibility:private", ], + enable_validation: false, } java_api_library { @@ -814,6 +821,7 @@ java_api_library { "android_module_lib_stubs_current.from-text", ], visibility: ["//visibility:public"], + enable_validation: false, } //////////////////////////////////////////////////////////////////////// diff --git a/core/api/current.txt b/core/api/current.txt index dfe023a3c181..f6564ecdbec0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -3286,10 +3286,10 @@ package android.accessibilityservice { public abstract class AccessibilityService extends android.app.Service { ctor public AccessibilityService(); - method @Deprecated public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl); - method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); - method @Deprecated public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl); - method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); + method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl); + method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); + method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl); + method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method public boolean clearCache(); method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo); method public final void disableSelf(); @@ -3401,9 +3401,9 @@ package android.accessibilityservice { field public static final int GLOBAL_ACTION_RECENTS = 3; // 0x3 field public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9; // 0x9 field public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; // 0x7 - field public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1 - field public static final int OVERLAY_RESULT_INVALID = 2; // 0x2 - field public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0 + field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1 + field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INVALID = 2; // 0x2 + field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0 field public static final String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService"; field public static final String SERVICE_META_DATA = "android.accessibilityservice"; field public static final int SHOW_MODE_AUTO = 0; // 0x0 @@ -9788,7 +9788,7 @@ package android.content { method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String); method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int); method @Deprecated @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource); - method @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource); + method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource); method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String); method @NonNull public android.content.AttributionSource.Builder setPid(int); } @@ -25787,15 +25787,15 @@ package android.media.midi { method public abstract void onDisconnect(android.media.midi.MidiReceiver); } - public abstract class MidiUmpDeviceService extends android.app.Service { + @FlaggedApi("com.android.media.midi.flags.virtual_ump") public abstract class MidiUmpDeviceService extends android.app.Service { ctor public MidiUmpDeviceService(); - method @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo(); - method @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers(); - method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); - method public void onClose(); - method public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus); - method @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers(); - field public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService"; + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo(); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers(); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") public void onClose(); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus); + method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers(); + field @FlaggedApi("com.android.media.midi.flags.virtual_ump") public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService"; } } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 9ecce144f9de..c72d09d66494 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3555,7 +3555,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 @FlaggedApi("android.content.pm.archiving") 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"; @@ -4028,7 +4028,7 @@ package android.content.pm { field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1 field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff field public static final int MATCH_ANY_USER = 4194304; // 0x400000 - field public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L + field @FlaggedApi("android.content.pm.archiving") public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000 field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 0e857a9c5f93..b9052873243e 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3185,7 +3185,6 @@ package android.telephony { field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2 field public static final int HAL_SERVICE_MODEM = 3; // 0x3 field public static final int HAL_SERVICE_NETWORK = 4; // 0x4 - field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int HAL_SERVICE_SATELLITE = 8; // 0x8 field public static final int HAL_SERVICE_SIM = 5; // 0x5 field public static final int HAL_SERVICE_VOICE = 6; // 0x6 field public static final android.util.Pair HAL_VERSION_UNKNOWN; @@ -3585,8 +3584,8 @@ package android.view { field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000 field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000 field public CharSequence accessibilityTitle; - field public float preferredMaxDisplayRefreshRate; - field public float preferredMinDisplayRefreshRate; + field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMaxDisplayRefreshRate; + field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMinDisplayRefreshRate; field public int privateFlags; } @@ -3616,7 +3615,6 @@ package android.view.accessibility { public final class AccessibilityWindowInfo implements android.os.Parcelable { method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger); - field public static final int UNDEFINED_WINDOW_ID = -1; // 0xffffffff } } diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 3370c121acfe..1000612ee0e2 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -23,6 +23,7 @@ import android.accessibilityservice.GestureDescription.MotionEventGenerator; import android.annotation.CallbackExecutor; import android.annotation.CheckResult; import android.annotation.ColorInt; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -793,6 +794,7 @@ public abstract class AccessibilityService extends Service { * @hide */ @Retention(RetentionPolicy.SOURCE) + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") @IntDef( prefix = {"OVERLAY_RESULT_"}, value = { @@ -803,6 +805,7 @@ public abstract class AccessibilityService extends Service { public @interface AttachOverlayResult {} /** Result code indicating the overlay was successfully attached. */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_SUCCESS = 0; /** @@ -810,6 +813,7 @@ public abstract class AccessibilityService extends Service { * error and not * because of problems with the input. */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; /** @@ -817,6 +821,7 @@ public abstract class AccessibilityService extends Service { * specified display or * window id was invalid. */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INVALID = 2; private int mConnectionId = AccessibilityInteractionClient.NO_ID; @@ -3506,11 +3511,7 @@ public abstract class AccessibilityService extends Service { * @param displayId the display to which the SurfaceControl should be attached. * @param sc the SurfaceControl containing the overlay content * - * @deprecated Use - * {@link #attachAccessibilityOverlayToDisplay(int, SurfaceControl, Executor, IntConsumer)} - * instead. */ - @Deprecated public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) { Preconditions.checkNotNull(sc, "SurfaceControl cannot be null"); AccessibilityInteractionClient.getInstance(this) @@ -3547,6 +3548,7 @@ public abstract class AccessibilityService extends Service { * @see #OVERLAY_RESULT_INVALID * @see #OVERLAY_RESULT_INTERNAL_ERROR */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToDisplay( int displayId, @NonNull SurfaceControl sc, @@ -3581,11 +3583,7 @@ public abstract class AccessibilityService extends Service { * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}. * @param sc the SurfaceControl containing the overlay content * - * @deprecated Use - * {@link #attachAccessibilityOverlayToWindow(int, SurfaceControl, Executor,IntConsumer)} - * instead. */ - @Deprecated public void attachAccessibilityOverlayToWindow( int accessibilityWindowId, @NonNull SurfaceControl sc) { Preconditions.checkNotNull(sc, "SurfaceControl cannot be null"); @@ -3623,6 +3621,7 @@ public abstract class AccessibilityService extends Service { * @see #OVERLAY_RESULT_INVALID * @see #OVERLAY_RESULT_INTERNAL_ERROR */ + @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToWindow( int accessibilityWindowId, @NonNull SurfaceControl sc, diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 15bd1dcc3980..e2689687f388 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -651,6 +651,7 @@ public class AssistStructure implements Parcelable { // POJO used to override some autofill-related values when the node is parcelized. // Not written to parcel. AutofillOverlay mAutofillOverlay; + boolean mIsCredential; int mX; int mY; @@ -799,6 +800,7 @@ public class AssistStructure implements Parcelable { if (autofillFlags != 0) { mSanitized = in.readInt() == 1; + mIsCredential = in.readInt() == 1; mImportantForAutofill = in.readInt(); if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) { @@ -1033,6 +1035,7 @@ public class AssistStructure implements Parcelable { if (autofillFlags != 0) { out.writeInt(mSanitized ? 1 : 0); + out.writeInt(mIsCredential ? 1 : 0); out.writeInt(mImportantForAutofill); writeSensitive = mSanitized || !sanitizeOnWrite; if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) { @@ -1246,6 +1249,19 @@ public class AssistStructure implements Parcelable { } /** + * @return whether the node is a credential. + * + * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes, + * not for assist purposes. + * TODO(b/303677885): add TestApi + * + * @hide + */ + public boolean isCredential() { + return mIsCredential; + } + + /** * Gets the {@link android.text.InputType} bits of this structure. * * @return bits as defined by {@link android.text.InputType}. @@ -2183,6 +2199,11 @@ public class AssistStructure implements Parcelable { } @Override + public void setIsCredential(boolean isCredential) { + mNode.mIsCredential = isCredential; + } + + @Override public void setReceiveContentMimeTypes(@Nullable String[] mimeTypes) { mNode.mReceiveContentMimeTypes = mimeTypes; } @@ -2498,7 +2519,9 @@ public class AssistStructure implements Parcelable { + ", value=" + node.getAutofillValue() + ", sanitized=" + node.isSanitized() + ", important=" + node.getImportantForAutofill() - + ", visibility=" + node.getVisibility()); + + ", visibility=" + node.getVisibility() + + ", isCredential=" + node.isCredential() + ); } final int NCHILDREN = node.getChildCount(); diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index 20eae9ade429..62fbcaff79e3 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -744,6 +744,7 @@ public final class AttributionSource implements Parcelable { /** * The next app to receive the permission protected data. */ + @FlaggedApi(Flags.FLAG_SET_NEXT_ATTRIBUTION_SOURCE) public @NonNull Builder setNextAttributionSource(@NonNull AttributionSource value) { checkNotUsed(); mBuilderFieldsSet |= 0x20; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 884351b57045..59bb73b5916d 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -323,7 +323,7 @@ public abstract class Context { // Make sure no flag uses the sign bit (most significant bit) of the long integer, // to avoid future confusion. BIND_BYPASS_USER_NETWORK_RESTRICTIONS, - BIND_FILTER_OUT_QUARANTINED_COMPONENTS, + BIND_MATCH_QUARANTINED_COMPONENTS, }) @Retention(RetentionPolicy.SOURCE) public @interface BindServiceFlagsLongBits {} @@ -703,7 +703,7 @@ public abstract class Context { * * @hide */ - public static final long BIND_FILTER_OUT_QUARANTINED_COMPONENTS = 0x2_0000_0000L; + public static final long BIND_MATCH_QUARANTINED_COMPONENTS = 0x2_0000_0000L; /** diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 1eb2cd174a7f..b765562ab587 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3929,6 +3929,8 @@ public class Intent implements Parcelable, Cloneable { * {@link #ACTION_BOOT_COMPLETED} is sent. This is sent as a foreground * broadcast, since it is part of a visible user interaction; be as quick * as possible when handling it. + * + * <p><b>Note:</b> This broadcast is not sent to the system user. */ public static final String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE"; @@ -5323,6 +5325,7 @@ public class Intent implements Parcelable, Cloneable { * @hide */ @SystemApi + @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; // --------------------------------------------------------------------- diff --git a/core/java/android/content/pm/ArchivedActivityParcel.aidl b/core/java/android/content/pm/ArchivedActivityParcel.aidl index 7ab7ed1cc5df..74953fff40d8 100644 --- a/core/java/android/content/pm/ArchivedActivityParcel.aidl +++ b/core/java/android/content/pm/ArchivedActivityParcel.aidl @@ -16,9 +16,12 @@ package android.content.pm; +import android.content.ComponentName; + /** @hide */ parcelable ArchivedActivityParcel { String title; + ComponentName originalComponentName; // PNG compressed bitmaps. byte[] iconBitmap; byte[] monochromeIconBitmap; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 4d5d05611d7a..1b60f8ed904f 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -838,7 +838,7 @@ public abstract class PackageManager { GET_DISABLED_COMPONENTS, GET_DISABLED_UNTIL_USED_COMPONENTS, GET_UNINSTALLED_PACKAGES, - FILTER_OUT_QUARANTINED_COMPONENTS, + MATCH_QUARANTINED_COMPONENTS, }) @Retention(RetentionPolicy.SOURCE) public @interface ComponentInfoFlagsBits {} @@ -863,7 +863,7 @@ public abstract class PackageManager { GET_DISABLED_UNTIL_USED_COMPONENTS, GET_UNINSTALLED_PACKAGES, MATCH_CLONE_PROFILE, - FILTER_OUT_QUARANTINED_COMPONENTS, + MATCH_QUARANTINED_COMPONENTS, }) @Retention(RetentionPolicy.SOURCE) public @interface ResolveInfoFlagsBits {} @@ -1252,12 +1252,13 @@ public abstract class PackageManager { */ // TODO(b/278553670) Unhide and update @links before launch. @SystemApi + @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32; /** * @hide */ - public static final long FILTER_OUT_QUARANTINED_COMPONENTS = 0x100000000L; + public static final long MATCH_QUARANTINED_COMPONENTS = 0x100000000L; /** * Flag for {@link #addCrossProfileIntentFilter}: if this flag is set: when diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index b2cc070228b7..db12728cfb98 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -50,3 +50,11 @@ flag { description: "Feature flag to enable the features that rely on new ART Service APIs that are in the VIC version of the ART module." bug: "304741685" } + +flag { + name: "sdk_lib_independence" + namespace: "package_manager_service" + description: "Feature flag to keep app working even if its declared sdk-library dependency is unavailable." + bug: "295827951" + is_fixed_read_only: true +} diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index a4593be8c2e4..aeddd0c8d4b1 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -31,6 +31,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.app.ActivityThread; import android.app.KeyguardManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -775,7 +776,8 @@ public final class DisplayManager { */ public void registerDisplayListener(@NonNull DisplayListener listener, @Nullable Handler handler, @EventsMask long eventsMask) { - mGlobal.registerDisplayListener(listener, handler, eventsMask); + mGlobal.registerDisplayListener(listener, handler, eventsMask, + ActivityThread.currentPackageName()); } /** diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 6d6085b471d9..8decd50664b3 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -25,6 +25,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.app.ActivityThread; import android.app.PropertyInvalidatedCache; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -45,8 +46,11 @@ import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.Trace; +import android.sysprop.DisplayProperties; +import android.text.TextUtils; import android.util.Log; import android.util.Pair; +import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DisplayAdjustments; @@ -72,7 +76,13 @@ import java.util.concurrent.atomic.AtomicLong; */ public final class DisplayManagerGlobal { private static final String TAG = "DisplayManager"; - private static final boolean DEBUG = false; + + private static final String EXTRA_LOGGING_PACKAGE_NAME = + DisplayProperties.debug_vri_package().orElse(null); + private static String sCurrentPackageName = ActivityThread.currentPackageName(); + private static boolean sExtraDisplayListenerLogging = initExtraLogging(); + + private static final boolean DEBUG = false || sExtraDisplayListenerLogging; // True if display info and display ids should be cached. // @@ -130,6 +140,8 @@ public final class DisplayManagerGlobal { @VisibleForTesting public DisplayManagerGlobal(IDisplayManager dm) { mDm = dm; + initExtraLogging(); + try { mWideColorSpace = ColorSpace.get( @@ -208,7 +220,7 @@ public final class DisplayManagerGlobal { registerCallbackIfNeededLocked(); - if (DEBUG) { + if (DEBUG || extraLogging()) { Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info); } return info; @@ -321,12 +333,14 @@ public final class DisplayManagerGlobal { * If null, listener will use the handler for the current thread, and if still null, * the handler for the main thread. * If that is still null, a runtime exception will be thrown. + * @param packageName of the calling package. */ public void registerDisplayListener(@NonNull DisplayListener listener, - @Nullable Handler handler, @EventsMask long eventsMask) { + @Nullable Handler handler, @EventsMask long eventsMask, String packageName) { Looper looper = getLooperForHandler(handler); Handler springBoard = new Handler(looper); - registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask); + registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask, + packageName); } /** @@ -334,9 +348,11 @@ public final class DisplayManagerGlobal { * * @param listener The listener that will be called when display changes occur. * @param executor Executor for the thread that will be receiving the callbacks. Cannot be null. + * @param eventsMask Mask of events to be listened to. + * @param packageName of the calling package. */ public void registerDisplayListener(@NonNull DisplayListener listener, - @NonNull Executor executor, @EventsMask long eventsMask) { + @NonNull Executor executor, @EventsMask long eventsMask, String packageName) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } @@ -345,15 +361,22 @@ public final class DisplayManagerGlobal { throw new IllegalArgumentException("The set of events to listen to must not be empty."); } + if (extraLogging()) { + Slog.i(TAG, "Registering Display Listener: " + + Long.toBinaryString(eventsMask) + ", packageName: " + packageName); + } + synchronized (mLock) { int index = findDisplayListenerLocked(listener); if (index < 0) { - mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask)); + mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask, + packageName)); registerCallbackIfNeededLocked(); } else { mDisplayListeners.get(index).setEventsMask(eventsMask); } updateCallbackIfNeededLocked(); + maybeLogAllDisplayListeners(); } } @@ -362,6 +385,10 @@ public final class DisplayManagerGlobal { throw new IllegalArgumentException("listener must not be null"); } + if (extraLogging()) { + Slog.i(TAG, "Unregistering Display Listener: " + listener); + } + synchronized (mLock) { int index = findDisplayListenerLocked(listener); if (index >= 0) { @@ -371,6 +398,18 @@ public final class DisplayManagerGlobal { updateCallbackIfNeededLocked(); } } + maybeLogAllDisplayListeners(); + } + + private void maybeLogAllDisplayListeners() { + if (!sExtraDisplayListenerLogging) { + return; + } + + Slog.i(TAG, "Currently Registered Display Listeners:"); + for (int i = 0; i < mDisplayListeners.size(); i++) { + Slog.i(TAG, i + ": " + mDisplayListeners.get(i)); + } } private static Looper getLooperForHandler(@Nullable Handler handler) { @@ -1148,15 +1187,20 @@ public final class DisplayManagerGlobal { private final DisplayInfo mDisplayInfo = new DisplayInfo(); private final Executor mExecutor; private AtomicLong mGenerationId = new AtomicLong(1); + private final String mPackageName; DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor, - @EventsMask long eventsMask) { + @EventsMask long eventsMask, String packageName) { mExecutor = executor; mListener = listener; mEventsMask = eventsMask; + mPackageName = packageName; } public void sendDisplayEvent(int displayId, @DisplayEvent int event, DisplayInfo info) { + if (extraLogging()) { + Slog.i(TAG, "Sending Display Event: " + eventToString(event)); + } long generationId = mGenerationId.get(); Message msg = Message.obtain(null, event, displayId, 0, info); mExecutor.execute(() -> { @@ -1177,6 +1221,14 @@ public final class DisplayManagerGlobal { } private void handleMessage(Message msg) { + if (extraLogging()) { + Slog.i(TAG, "DisplayListenerDelegate(" + eventToString(msg.what) + + ", display=" + msg.arg1 + + ", mEventsMask=" + Long.toBinaryString(mEventsMask) + + ", mPackageName=" + mPackageName + + ", msg.obj=" + msg.obj + + ", listener=" + mListener.getClass() + ")"); + } if (DEBUG) { Trace.beginSection( "DisplayListenerDelegate(" + eventToString(msg.what) @@ -1193,6 +1245,10 @@ public final class DisplayManagerGlobal { if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) { DisplayInfo newInfo = (DisplayInfo) msg.obj; if (newInfo != null && !newInfo.equals(mDisplayInfo)) { + if (extraLogging()) { + Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: " + + newInfo); + } mDisplayInfo.copyFrom(newInfo); mListener.onDisplayChanged(msg.arg1); } @@ -1228,6 +1284,11 @@ public final class DisplayManagerGlobal { Trace.endSection(); } } + + @Override + public String toString() { + return "mask: {" + mEventsMask + "}, for " + mListener.getClass(); + } } /** @@ -1353,4 +1414,19 @@ public final class DisplayManagerGlobal { } return "UNKNOWN"; } + + + private static boolean initExtraLogging() { + if (sCurrentPackageName == null) { + sCurrentPackageName = ActivityThread.currentPackageName(); + sExtraDisplayListenerLogging = !TextUtils.isEmpty(EXTRA_LOGGING_PACKAGE_NAME) + && EXTRA_LOGGING_PACKAGE_NAME.equals(sCurrentPackageName); + } + return sExtraDisplayListenerLogging; + } + + private static boolean extraLogging() { + return sExtraDisplayListenerLogging && EXTRA_LOGGING_PACKAGE_NAME.equals( + sCurrentPackageName); + } } diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 37559b32e841..7fceda4a4393 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -16,7 +16,7 @@ flag { flag { name: "remove_app_profiler_pss_collection" - namespace: "android_platform_power_optimization" + namespace: "power_optimization" description: "Replaces background PSS collection in AppProfiler with RSS" bug: "297542292" } diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index d8534dd5fbc1..d60d4c6372fe 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -21,3 +21,10 @@ flag { description: "enable role controller in system server" bug: "302562590" } + +flag { + name: "set_next_attribution_source" + namespace: "permissions" + description: "enable AttributionSource.setNextAttributionSource" + bug: "304478648" +} diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 3f41c56ac7f1..d2806217a276 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -520,7 +520,7 @@ public class VoiceInteractionService extends Service { @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { - // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning + // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); @@ -546,6 +546,10 @@ public class VoiceInteractionService extends Service { @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, + // SoundTrigger.ModuleProperties, AlwaysOnHotwordDetector.Callback)} and replace with the + // permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); @@ -612,6 +616,11 @@ public class VoiceInteractionService extends Service { @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, + // AlwaysOnHotwordDetector.Callback)} and replace with the permission + // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. + return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, /* supportHotwordDetectionService= */ true, options, sharedMemory, /* modulProperties */ null, /* executor= */ null, callback); @@ -663,7 +672,11 @@ public class VoiceInteractionService extends Service { @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { - // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning + // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, + // Executor, AlwaysOnHotwordDetector.Callback)} and replace with the permission + // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); @@ -690,6 +703,10 @@ public class VoiceInteractionService extends Service { @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback) { + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, PersistableBundle, + // SharedMemory, SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} + // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. Objects.requireNonNull(keyphrase); Objects.requireNonNull(locale); diff --git a/core/java/android/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java index a89f79540c65..dc41b70d1683 100644 --- a/core/java/android/view/ContentRecordingSession.java +++ b/core/java/android/view/ContentRecordingSession.java @@ -52,6 +52,12 @@ public final class ContentRecordingSession implements Parcelable { */ public static final int RECORD_CONTENT_TASK = 1; + /** Full screen sharing (app is not selected). */ + public static final int TARGET_UID_FULL_SCREEN = -1; + + /** Can't report (e.g. side loaded app). */ + public static final int TARGET_UID_UNKNOWN = -2; + /** * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has * recorded content rendered to its surface. @@ -89,27 +95,36 @@ public final class ContentRecordingSession implements Parcelable { */ private boolean mWaitingForConsent = false; + /** UID of the package that is captured if selected. */ + private int mTargetUid = TARGET_UID_UNKNOWN; + /** * Default instance, with recording the display. */ private ContentRecordingSession() { } - /** - * Returns an instance initialized for recording the indicated display. - */ + /** Returns an instance initialized for recording the indicated display. */ public static ContentRecordingSession createDisplaySession(int displayToMirror) { - return new ContentRecordingSession().setDisplayToRecord(displayToMirror) - .setContentToRecord(RECORD_CONTENT_DISPLAY); + return new ContentRecordingSession() + .setDisplayToRecord(displayToMirror) + .setContentToRecord(RECORD_CONTENT_DISPLAY) + .setTargetUid(TARGET_UID_FULL_SCREEN); } - /** - * Returns an instance initialized for task recording. - */ + /** Returns an instance initialized for task recording. */ public static ContentRecordingSession createTaskSession( @NonNull IBinder taskWindowContainerToken) { - return new ContentRecordingSession().setContentToRecord(RECORD_CONTENT_TASK) - .setTokenToRecord(taskWindowContainerToken); + return createTaskSession(taskWindowContainerToken, TARGET_UID_UNKNOWN); + } + + /** Returns an instance initialized for task recording. */ + public static ContentRecordingSession createTaskSession( + @NonNull IBinder taskWindowContainerToken, int targetUid) { + return new ContentRecordingSession() + .setContentToRecord(RECORD_CONTENT_TASK) + .setTokenToRecord(taskWindowContainerToken) + .setTargetUid(targetUid); } /** @@ -175,13 +190,33 @@ public final class ContentRecordingSession implements Parcelable { } } + @IntDef(prefix = "TARGET_UID_", value = { + TARGET_UID_FULL_SCREEN, + TARGET_UID_UNKNOWN + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface TargetUid {} + + @DataClass.Generated.Member + public static String targetUidToString(@TargetUid int value) { + switch (value) { + case TARGET_UID_FULL_SCREEN: + return "TARGET_UID_FULL_SCREEN"; + case TARGET_UID_UNKNOWN: + return "TARGET_UID_UNKNOWN"; + default: return Integer.toHexString(value); + } + } + @DataClass.Generated.Member /* package-private */ ContentRecordingSession( int virtualDisplayId, @RecordContent int contentToRecord, int displayToRecord, @Nullable IBinder tokenToRecord, - boolean waitingForConsent) { + boolean waitingForConsent, + int targetUid) { this.mVirtualDisplayId = virtualDisplayId; this.mContentToRecord = contentToRecord; @@ -196,6 +231,7 @@ public final class ContentRecordingSession implements Parcelable { this.mDisplayToRecord = displayToRecord; this.mTokenToRecord = tokenToRecord; this.mWaitingForConsent = waitingForConsent; + this.mTargetUid = targetUid; // onConstructed(); // You can define this method to get a callback } @@ -251,6 +287,14 @@ public final class ContentRecordingSession implements Parcelable { } /** + * UID of the package that is captured if selected. + */ + @DataClass.Generated.Member + public int getTargetUid() { + return mTargetUid; + } + + /** * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has * recorded content rendered to its surface. */ @@ -314,6 +358,15 @@ public final class ContentRecordingSession implements Parcelable { return this; } + /** + * UID of the package that is captured if selected. + */ + @DataClass.Generated.Member + public @NonNull ContentRecordingSession setTargetUid( int value) { + mTargetUid = value; + return this; + } + @Override @DataClass.Generated.Member public String toString() { @@ -325,7 +378,8 @@ public final class ContentRecordingSession implements Parcelable { "contentToRecord = " + recordContentToString(mContentToRecord) + ", " + "displayToRecord = " + mDisplayToRecord + ", " + "tokenToRecord = " + mTokenToRecord + ", " + - "waitingForConsent = " + mWaitingForConsent + + "waitingForConsent = " + mWaitingForConsent + ", " + + "targetUid = " + mTargetUid + " }"; } @@ -346,7 +400,8 @@ public final class ContentRecordingSession implements Parcelable { && mContentToRecord == that.mContentToRecord && mDisplayToRecord == that.mDisplayToRecord && java.util.Objects.equals(mTokenToRecord, that.mTokenToRecord) - && mWaitingForConsent == that.mWaitingForConsent; + && mWaitingForConsent == that.mWaitingForConsent + && mTargetUid == that.mTargetUid; } @Override @@ -361,6 +416,7 @@ public final class ContentRecordingSession implements Parcelable { _hash = 31 * _hash + mDisplayToRecord; _hash = 31 * _hash + java.util.Objects.hashCode(mTokenToRecord); _hash = 31 * _hash + Boolean.hashCode(mWaitingForConsent); + _hash = 31 * _hash + mTargetUid; return _hash; } @@ -378,6 +434,7 @@ public final class ContentRecordingSession implements Parcelable { dest.writeInt(mContentToRecord); dest.writeInt(mDisplayToRecord); if (mTokenToRecord != null) dest.writeStrongBinder(mTokenToRecord); + dest.writeInt(mTargetUid); } @Override @@ -397,6 +454,7 @@ public final class ContentRecordingSession implements Parcelable { int contentToRecord = in.readInt(); int displayToRecord = in.readInt(); IBinder tokenToRecord = (flg & 0x8) == 0 ? null : (IBinder) in.readStrongBinder(); + int targetUid = in.readInt(); this.mVirtualDisplayId = virtualDisplayId; this.mContentToRecord = contentToRecord; @@ -412,6 +470,7 @@ public final class ContentRecordingSession implements Parcelable { this.mDisplayToRecord = displayToRecord; this.mTokenToRecord = tokenToRecord; this.mWaitingForConsent = waitingForConsent; + this.mTargetUid = targetUid; // onConstructed(); // You can define this method to get a callback } @@ -442,6 +501,7 @@ public final class ContentRecordingSession implements Parcelable { private int mDisplayToRecord; private @Nullable IBinder mTokenToRecord; private boolean mWaitingForConsent; + private int mTargetUid; private long mBuilderFieldsSet = 0L; @@ -513,10 +573,21 @@ public final class ContentRecordingSession implements Parcelable { return this; } + /** + * UID of the package that is captured if selected. + */ + @DataClass.Generated.Member + public @NonNull Builder setTargetUid(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mTargetUid = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull ContentRecordingSession build() { checkNotUsed(); - mBuilderFieldsSet |= 0x20; // Mark builder used + mBuilderFieldsSet |= 0x40; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mVirtualDisplayId = INVALID_DISPLAY; @@ -533,17 +604,21 @@ public final class ContentRecordingSession implements Parcelable { if ((mBuilderFieldsSet & 0x10) == 0) { mWaitingForConsent = false; } + if ((mBuilderFieldsSet & 0x20) == 0) { + mTargetUid = TARGET_UID_UNKNOWN; + } ContentRecordingSession o = new ContentRecordingSession( mVirtualDisplayId, mContentToRecord, mDisplayToRecord, mTokenToRecord, - mWaitingForConsent); + mWaitingForConsent, + mTargetUid); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x20) != 0) { + if ((mBuilderFieldsSet & 0x40) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -551,10 +626,10 @@ public final class ContentRecordingSession implements Parcelable { } @DataClass.Generated( - time = 1683628463074L, + time = 1697456140720L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/ContentRecordingSession.java", - inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\nprivate int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingForConsent\npublic static android.view.ContentRecordingSession createDisplaySession(int)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)") + inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\npublic static final int TARGET_UID_FULL_SCREEN\npublic static final int TARGET_UID_UNKNOWN\nprivate int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingForConsent\nprivate int mTargetUid\npublic static android.view.ContentRecordingSession createDisplaySession(int)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder,int)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 0b2b6ce33666..506945501609 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -26,6 +26,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.TestApi; +import android.app.ActivityThread; import android.app.KeyguardManager; import android.app.WindowConfiguration; import android.compat.annotation.UnsupportedAppUsage; @@ -1366,7 +1367,8 @@ public final class Display { // form of the larger DISPLAY_CHANGED event mGlobal.registerDisplayListener(toRegister, executor, DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED - | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED, + ActivityThread.currentPackageName()); } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 16318e0a3f49..e9d0e4c557c6 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -9308,6 +9308,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, structure.setAutofillType(autofillType); structure.setAutofillHints(getAutofillHints()); structure.setAutofillValue(getAutofillValue()); + structure.setIsCredential(isCredential()); } structure.setImportantForAutofill(getImportantForAutofill()); structure.setReceiveContentMimeTypes(getReceiveContentMimeTypes()); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index b5648cc90dcd..939278314df4 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1549,7 +1549,8 @@ public final class ViewRootImpl implements ViewParent, mHandler, DisplayManager.EVENT_FLAG_DISPLAY_ADDED | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, + mBasePackageName); } /** diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index 2c2ae06e9186..bb2c7c8b198b 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -397,6 +397,13 @@ public abstract class ViewStructure { public void setImportantForAutofill(@AutofillImportance int mode) {} /** + * Sets whether the node is a credential. See {@link View#isCredential}. + * + * @hide + */ + public void setIsCredential(boolean isCredential) {} + + /** * Sets the MIME types accepted by this view. See {@link View#getReceiveContentMimeTypes()}. * * <p>Should only be set when the node is used for Autofill or Content Capture purposes - it diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index a3b93b433dda..4f03ce98ccac 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -17,6 +17,7 @@ package android.view; import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT; +import static android.view.flags.Flags.FLAG_WM_DISPLAY_REFRESH_RATE_TEST; import static android.view.View.STATUS_BAR_DISABLE_BACK; import static android.view.View.STATUS_BAR_DISABLE_CLOCK; import static android.view.View.STATUS_BAR_DISABLE_EXPAND; @@ -3905,6 +3906,7 @@ public interface WindowManager extends ViewManager { * This value is ignored if {@link #preferredDisplayModeId} is set. * @hide */ + @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST) @TestApi public float preferredMinDisplayRefreshRate; @@ -3914,6 +3916,7 @@ public interface WindowManager extends ViewManager { * This value is ignored if {@link #preferredDisplayModeId} is set. * @hide */ + @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST) @TestApi public float preferredMaxDisplayRefreshRate; diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 60f46e60ec9e..12ce0f47c460 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -1731,6 +1731,9 @@ public final class AccessibilityInteractionClient @Override public void sendAttachOverlayResult( @AccessibilityService.AttachOverlayResult int result, int interactionId) { + if (!Flags.a11yOverlayCallbacks()) { + return; + } synchronized (mInstanceLock) { if (mAttachAccessibilityOverlayCallbacks.contains(interactionId)) { final Pair<Executor, IntConsumer> pair = diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index 09b2f9c1ee15..fa0052cf664a 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -99,7 +99,6 @@ public final class AccessibilityWindowInfo implements Parcelable { /** @hide */ public static final int UNDEFINED_CONNECTION_ID = -1; /** @hide */ - @TestApi public static final int UNDEFINED_WINDOW_ID = -1; /** @hide */ public static final int ANY_WINDOW_ID = -2; diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 85dadd4a061d..cc612ed93b2f 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -13,3 +13,10 @@ flag { description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen" bug: "303871725" } + +flag { + name: "a11y_overlay_callbacks" + namespace: "accessibility" + description: "Whether to allow the passing of result callbacks when attaching a11y overlays." + bug: "304478691" +} diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 2241fd5dc37a..b44d6a496058 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -317,16 +317,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } } - // Should not be possible for mComponentName to be null here but check anyway - if (mManager.mOptions.contentProtectionOptions.enableReceiver - && mManager.getContentProtectionEventBuffer() != null - && mComponentName != null) { + if (isContentProtectionEnabled()) { mContentProtectionEventProcessor = new ContentProtectionEventProcessor( mManager.getContentProtectionEventBuffer(), mHandler, mSystemServerInterface, - mComponentName.getPackageName()); + mComponentName.getPackageName(), + mManager.mOptions.contentProtectionOptions); } else { mContentProtectionEventProcessor = null; } @@ -956,4 +954,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession { private boolean isContentCaptureReceiverEnabled() { return mManager.mOptions.enableReceiver; } + + @UiThread + private boolean isContentProtectionEnabled() { + // Should not be possible for mComponentName to be null here but check anyway + // Should not be possible for groups to be empty if receiver is enabled but check anyway + return mManager.mOptions.contentProtectionOptions.enableReceiver + && mManager.getContentProtectionEventBuffer() != null + && mComponentName != null + && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty() + || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty()); + } } diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java index b44abf3eb04d..aaf90bd00535 100644 --- a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java +++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java @@ -19,9 +19,9 @@ package android.view.contentprotection; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiThread; +import android.content.ContentCaptureOptions; import android.content.pm.ParceledListSlice; import android.os.Handler; -import android.text.InputType; import android.util.Log; import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.IContentCaptureManager; @@ -33,10 +33,10 @@ import com.android.internal.util.RingBuffer; import java.time.Duration; import java.time.Instant; import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; +import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.stream.Stream; /** * Main entry point for processing {@link ContentCaptureEvent} for the content protection flow. @@ -47,33 +47,13 @@ public class ContentProtectionEventProcessor { private static final String TAG = "ContentProtectionEventProcessor"; - private static final List<Integer> PASSWORD_FIELD_INPUT_TYPES = - Collections.unmodifiableList( - Arrays.asList( - InputType.TYPE_NUMBER_VARIATION_PASSWORD, - InputType.TYPE_TEXT_VARIATION_PASSWORD, - InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD, - InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD)); - - private static final List<String> PASSWORD_TEXTS = - Collections.unmodifiableList( - Arrays.asList("password", "pass word", "code", "pin", "credential")); - - private static final List<String> ADDITIONAL_SUSPICIOUS_TEXTS = - Collections.unmodifiableList( - Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in")); - private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3); - private static final String ANDROID_CLASS_NAME_PREFIX = "android."; - private static final Set<Integer> EVENT_TYPES_TO_STORE = - Collections.unmodifiableSet( - new HashSet<>( - Arrays.asList( - ContentCaptureEvent.TYPE_VIEW_APPEARED, - ContentCaptureEvent.TYPE_VIEW_DISAPPEARED, - ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED))); + Set.of( + ContentCaptureEvent.TYPE_VIEW_APPEARED, + ContentCaptureEvent.TYPE_VIEW_DISAPPEARED, + ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED); private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150; @@ -85,11 +65,7 @@ public class ContentProtectionEventProcessor { @NonNull private final String mPackageName; - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public boolean mPasswordFieldDetected = false; - - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - public boolean mSuspiciousTextDetected = false; + @NonNull private final ContentCaptureOptions.ContentProtectionOptions mOptions; @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @Nullable @@ -97,15 +73,32 @@ public class ContentProtectionEventProcessor { private int mResetLoginRemainingEventsToProcess; + private boolean mAnyGroupFound = false; + + // Ordered by priority + private final List<SearchGroup> mGroupsRequired; + + // Ordered by priority + private final List<SearchGroup> mGroupsOptional; + + // Ordered by priority + private final List<SearchGroup> mGroupsAll; + public ContentProtectionEventProcessor( @NonNull RingBuffer<ContentCaptureEvent> eventBuffer, @NonNull Handler handler, @NonNull IContentCaptureManager contentCaptureManager, - @NonNull String packageName) { + @NonNull String packageName, + @NonNull ContentCaptureOptions.ContentProtectionOptions options) { mEventBuffer = eventBuffer; mHandler = handler; mContentCaptureManager = contentCaptureManager; mPackageName = packageName; + mOptions = options; + mGroupsRequired = options.requiredGroups.stream().map(SearchGroup::new).toList(); + mGroupsOptional = options.optionalGroups.stream().map(SearchGroup::new).toList(); + mGroupsAll = + Stream.of(mGroupsRequired, mGroupsOptional).flatMap(Collection::stream).toList(); } /** Main entry point for {@link ContentCaptureEvent} processing. */ @@ -130,9 +123,31 @@ public class ContentProtectionEventProcessor { @UiThread private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) { - mPasswordFieldDetected |= isPasswordField(event); - mSuspiciousTextDetected |= isSuspiciousText(event); - if (mPasswordFieldDetected && mSuspiciousTextDetected) { + ViewNode viewNode = event.getViewNode(); + String eventText = ContentProtectionUtils.getEventTextLower(event); + String viewNodeText = ContentProtectionUtils.getViewNodeTextLower(viewNode); + String hintText = ContentProtectionUtils.getHintTextLower(viewNode); + + mGroupsAll.stream() + .filter(group -> !group.mFound) + .filter( + group -> + group.matches(eventText) + || group.matches(viewNodeText) + || group.matches(hintText)) + .findFirst() + .ifPresent( + group -> { + group.mFound = true; + mAnyGroupFound = true; + }); + + boolean loginDetected = + mGroupsRequired.stream().allMatch(group -> group.mFound) + && mGroupsOptional.stream().filter(group -> group.mFound).count() + >= mOptions.optionalGroupsThreshold; + + if (loginDetected) { loginDetected(); } else { maybeResetLoginFlags(); @@ -150,14 +165,13 @@ public class ContentProtectionEventProcessor { @UiThread private void resetLoginFlags() { - mPasswordFieldDetected = false; - mSuspiciousTextDetected = false; - mResetLoginRemainingEventsToProcess = 0; + mGroupsAll.forEach(group -> group.mFound = false); + mAnyGroupFound = false; } @UiThread private void maybeResetLoginFlags() { - if (mPasswordFieldDetected || mSuspiciousTextDetected) { + if (mAnyGroupFound) { if (mResetLoginRemainingEventsToProcess <= 0) { mResetLoginRemainingEventsToProcess = RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; } else { @@ -194,61 +208,21 @@ public class ContentProtectionEventProcessor { } } - private boolean isPasswordField(@NonNull ContentCaptureEvent event) { - return isPasswordField(event.getViewNode()); - } - - private boolean isPasswordField(@Nullable ViewNode viewNode) { - if (viewNode == null) { - return false; - } - return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode); - } - - private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) { - if (!isAndroidViewNode(viewNode)) { - return false; - } - int inputType = viewNode.getInputType(); - return PASSWORD_FIELD_INPUT_TYPES.stream() - .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0); - } + private static final class SearchGroup { - private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) { - if (viewNode.getClassName() != null) { - return false; - } - return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode)); - } + @NonNull private final List<String> mSearchStrings; - private boolean isAndroidViewNode(@NonNull ViewNode viewNode) { - String className = viewNode.getClassName(); - return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX); - } - - private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) { - return isSuspiciousText(ContentProtectionUtils.getEventText(event)) - || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event)); - } + public boolean mFound = false; - private boolean isSuspiciousText(@Nullable String text) { - if (text == null) { - return false; + SearchGroup(@NonNull List<String> searchStrings) { + mSearchStrings = searchStrings; } - if (isPasswordText(text)) { - return true; - } - String lowerCaseText = text.toLowerCase(); - return ADDITIONAL_SUSPICIOUS_TEXTS.stream() - .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText)); - } - private boolean isPasswordText(@Nullable String text) { - if (text == null) { - return false; + public boolean matches(@Nullable String text) { + if (text == null) { + return false; + } + return mSearchStrings.stream().anyMatch(text::contains); } - String lowerCaseText = text.toLowerCase(); - return PASSWORD_TEXTS.stream() - .anyMatch(passwordText -> lowerCaseText.contains(passwordText)); } } diff --git a/core/java/android/view/contentprotection/ContentProtectionUtils.java b/core/java/android/view/contentprotection/ContentProtectionUtils.java index 9abf6f10d05d..1ecac7f188ab 100644 --- a/core/java/android/view/contentprotection/ContentProtectionUtils.java +++ b/core/java/android/view/contentprotection/ContentProtectionUtils.java @@ -28,33 +28,39 @@ import android.view.contentcapture.ViewNode; */ public final class ContentProtectionUtils { - /** Returns the text extracted directly from the {@link ContentCaptureEvent}, if set. */ + /** Returns the lowercase text extracted from the {@link ContentCaptureEvent}, if set. */ @Nullable - public static String getEventText(@NonNull ContentCaptureEvent event) { + public static String getEventTextLower(@NonNull ContentCaptureEvent event) { CharSequence text = event.getText(); if (text == null) { return null; } - return text.toString(); + return text.toString().toLowerCase(); } - /** Returns the text extracted from the event's {@link ViewNode}, if set. */ + /** Returns the lowercase text extracted from the {@link ViewNode}, if set. */ @Nullable - public static String getViewNodeText(@NonNull ContentCaptureEvent event) { - ViewNode viewNode = event.getViewNode(); + public static String getViewNodeTextLower(@Nullable ViewNode viewNode) { if (viewNode == null) { return null; } - return getViewNodeText(viewNode); + CharSequence text = viewNode.getText(); + if (text == null) { + return null; + } + return text.toString().toLowerCase(); } - /** Returns the text extracted directly from the {@link ViewNode}, if set. */ + /** Returns the lowercase hint text extracted from the {@link ViewNode}, if set. */ @Nullable - public static String getViewNodeText(@NonNull ViewNode viewNode) { - CharSequence text = viewNode.getText(); + public static String getHintTextLower(@Nullable ViewNode viewNode) { + if (viewNode == null) { + return null; + } + String text = viewNode.getHint(); if (text == null) { return null; } - return text.toString(); + return text.toLowerCase(); } } diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig index 56b5fac1c64a..fd9689013af3 100644 --- a/core/java/android/view/flags/refresh_rate_flags.aconfig +++ b/core/java/android/view/flags/refresh_rate_flags.aconfig @@ -26,4 +26,11 @@ flag { namespace: "core_graphics" description: "Enable the `setFrameRate` callback" bug: "299946220" +} + +flag { + name: "wm_display_refresh_rate_test" + namespace: "core_graphics" + description: "Adds WindowManager display refresh rate fields to test API" + bug: "304475199" }
\ No newline at end of file diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java index 5d14698c82b3..eb9d62bb9438 100644 --- a/core/java/android/window/SurfaceSyncGroup.java +++ b/core/java/android/window/SurfaceSyncGroup.java @@ -263,6 +263,7 @@ public final class SurfaceSyncGroup { Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, "markSyncReady"); } synchronized (mLock) { + toggleTimeout(false); if (mHasWMSync) { try { WindowManagerGlobal.getWindowManagerService().markSurfaceSyncGroupReady(mToken); diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java index 43fa0be6c1b7..4e0f9a51c0a0 100644 --- a/core/java/android/window/TaskFragmentOperation.java +++ b/core/java/android/window/TaskFragmentOperation.java @@ -88,6 +88,26 @@ public final class TaskFragmentOperation implements Parcelable { */ public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11; + /** + * Reorders the TaskFragment to be the bottom-most in the Task. Note that this op will bring the + * TaskFragment to the bottom of the Task below all the other Activities and TaskFragments. + * + * This is only allowed for system organizers. See + * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer( + * ITaskFragmentOrganizer, boolean)} + */ + public static final int OP_TYPE_REORDER_TO_BOTTOM_OF_TASK = 12; + + /** + * Reorders the TaskFragment to be the top-most in the Task. Note that this op will bring the + * TaskFragment to the top of the Task above all the other Activities and TaskFragments. + * + * This is only allowed for system organizers. See + * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer( + * ITaskFragmentOrganizer, boolean)} + */ + public static final int OP_TYPE_REORDER_TO_TOP_OF_TASK = 13; + @IntDef(prefix = { "OP_TYPE_" }, value = { OP_TYPE_UNKNOWN, OP_TYPE_CREATE_TASK_FRAGMENT, @@ -101,7 +121,9 @@ public final class TaskFragmentOperation implements Parcelable { OP_TYPE_SET_ANIMATION_PARAMS, OP_TYPE_SET_RELATIVE_BOUNDS, OP_TYPE_REORDER_TO_FRONT, - OP_TYPE_SET_ISOLATED_NAVIGATION + OP_TYPE_SET_ISOLATED_NAVIGATION, + OP_TYPE_REORDER_TO_BOTTOM_OF_TASK, + OP_TYPE_REORDER_TO_TOP_OF_TASK, }) @Retention(RetentionPolicy.SOURCE) public @interface OperationType {} diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java index 72a1bac28c9f..ca6c54dc0285 100644 --- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java +++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java @@ -24,6 +24,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import android.annotation.IntDef; import android.annotation.Nullable; +import android.app.ActivityThread; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; @@ -147,7 +148,8 @@ public class DisplayResolutionTracker { public void registerDisplayListener(DisplayManager.DisplayListener listener) { manager.registerDisplayListener(listener, handler, DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED); + | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, + ActivityThread.currentPackageName()); } @Override diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto index eb14db070f9f..068f4dd07ccb 100644 --- a/core/proto/android/service/package.proto +++ b/core/proto/android/service/package.proto @@ -115,6 +115,9 @@ message PackageProto { // Only set if the app defined a monochrome icon. optional string monochrome_icon_bitmap_path = 3; + + // The component name of the original activity (pre-archival). + optional string original_component_name = 4; } /** Information about main activities. */ diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java index 149f58f0a69b..c2e6b60cd680 100644 --- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java +++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java @@ -42,8 +42,8 @@ import org.mockito.MockitoAnnotations; public class DisplayManagerGlobalTest { private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; + | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; @Mock private IDisplayManager mDisplayManager; @@ -69,7 +69,8 @@ public class DisplayManagerGlobalTest { @Test public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException { - mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS); + mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS, + null); Mockito.verify(mDisplayManager) .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong()); IDisplayManagerCallback callback = mCallbackCaptor.getValue(); @@ -97,26 +98,27 @@ public class DisplayManagerGlobalTest { public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException { // First we subscribe to all events in order to test that the subsequent calls to // registerDisplayListener will update the event mask. - mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS); + mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS, + null); Mockito.verify(mDisplayManager) .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong()); IDisplayManagerCallback callback = mCallbackCaptor.getValue(); int displayId = 1; mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, - ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED); + ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED, null); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); waitForHandler(); Mockito.verifyZeroInteractions(mListener); mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, - ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED); + ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); waitForHandler(); Mockito.verifyZeroInteractions(mListener); mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, - ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); + ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); waitForHandler(); Mockito.verifyZeroInteractions(mListener); @@ -139,7 +141,7 @@ public class DisplayManagerGlobalTest { public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners() throws RemoteException { mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, - DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS); + DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS, null); InOrder inOrder = Mockito.inOrder(mDisplayManager); inOrder.verify(mDisplayManager) @@ -163,6 +165,7 @@ public class DisplayManagerGlobalTest { } private void waitForHandler() { - mHandler.runWithScissors(() -> { }, 0); + mHandler.runWithScissors(() -> { + }, 0); } } diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java index e76d266c614c..d47d7891d0e4 100644 --- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java @@ -115,6 +115,26 @@ public class MainContentCaptureSessionTest { new ContentCaptureOptions.ContentProtectionOptions( /* enableReceiver= */ true, -BUFFER_SIZE, + /* requiredGroups= */ List.of(List.of("a")), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); + MainContentCaptureSession session = createSession(options); + session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + + session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); + + assertThat(session.mContentProtectionEventProcessor).isNull(); + verifyZeroInteractions(mMockContentProtectionEventProcessor); + } + + @Test + public void onSessionStarted_contentProtectionNoGroups_processorNotCreated() { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + new ContentCaptureOptions.ContentProtectionOptions( + /* enableReceiver= */ true, + BUFFER_SIZE, /* requiredGroups= */ Collections.emptyList(), /* optionalGroups= */ Collections.emptyList(), /* optionalGroupsThreshold= */ 0)); @@ -320,7 +340,7 @@ public class MainContentCaptureSessionTest { new ContentCaptureOptions.ContentProtectionOptions( enableContentProtectionReceiver, BUFFER_SIZE, - /* requiredGroups= */ Collections.emptyList(), + /* requiredGroups= */ List.of(List.of("a")), /* optionalGroups= */ Collections.emptyList(), /* optionalGroupsThreshold= */ 0)); } diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java index 39a2e0e048e8..ba0dbf454ad2 100644 --- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java +++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java @@ -29,12 +29,13 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ContentCaptureOptions; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.Handler; -import android.os.Looper; -import android.text.InputType; +import android.os.test.TestLooper; import android.view.View; import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.IContentCaptureManager; @@ -57,6 +58,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import org.mockito.verification.VerificationMode; import java.time.Instant; import java.util.ArrayList; @@ -75,13 +77,25 @@ public class ContentProtectionEventProcessorTest { private static final String PACKAGE_NAME = "com.test.package.name"; - private static final String ANDROID_CLASS_NAME = "android.test.some.class.name"; + private static final String TEXT_REQUIRED1 = "TEXT REQUIRED1 TEXT"; - private static final String PASSWORD_TEXT = "ENTER PASSWORD HERE"; + private static final String TEXT_REQUIRED2 = "TEXT REQUIRED2 TEXT"; - private static final String SUSPICIOUS_TEXT = "PLEASE SIGN IN"; + private static final String TEXT_OPTIONAL1 = "TEXT OPTIONAL1 TEXT"; - private static final String SAFE_TEXT = "SAFE TEXT"; + private static final String TEXT_OPTIONAL2 = "TEXT OPTIONAL2 TEXT"; + + private static final String TEXT_CONTAINS_OPTIONAL3 = "TEXTOPTIONAL3TEXT"; + + private static final String TEXT_SHARED = "TEXT SHARED TEXT"; + + private static final String TEXT_SAFE = "TEXT SAFE TEXT"; + + private static final List<List<String>> REQUIRED_GROUPS = + List.of(List.of("required1", "missing"), List.of("required2", "shared")); + + private static final List<List<String>> OPTIONAL_GROUPS = + List.of(List.of("optional1"), List.of("optional2", "optional3"), List.of("shared")); private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent(); @@ -91,7 +105,17 @@ public class ContentProtectionEventProcessorTest { private static final Set<Integer> EVENT_TYPES_TO_STORE = ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED); - private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150; + private static final int BUFFER_SIZE = 150; + + private static final int OPTIONAL_GROUPS_THRESHOLD = 1; + + private static final ContentCaptureOptions.ContentProtectionOptions OPTIONS = + new ContentCaptureOptions.ContentProtectionOptions( + /* enableReceiver= */ true, + BUFFER_SIZE, + REQUIRED_GROUPS, + OPTIONAL_GROUPS, + OPTIONAL_GROUPS_THRESHOLD); @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @@ -101,16 +125,19 @@ public class ContentProtectionEventProcessorTest { private final Context mContext = ApplicationProvider.getApplicationContext(); - private ContentProtectionEventProcessor mContentProtectionEventProcessor; + private final TestLooper mTestLooper = new TestLooper(); + + @NonNull private ContentProtectionEventProcessor mContentProtectionEventProcessor; @Before public void setup() { mContentProtectionEventProcessor = new ContentProtectionEventProcessor( mMockEventBuffer, - new Handler(Looper.getMainLooper()), + new Handler(mTestLooper.getLooper()), mMockContentCaptureManager, - PACKAGE_NAME); + PACKAGE_NAME, + OPTIONS); } @Test @@ -156,347 +183,224 @@ public class ContentProtectionEventProcessorTest { } @Test - public void processEvent_loginDetected_inspectsOnlyTypeViewAppeared() { - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - - for (int type = -100; type <= 100; type++) { - if (type == TYPE_VIEW_APPEARED) { - continue; - } - - mContentProtectionEventProcessor.processEvent(createEvent(type)); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - } - - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } - - @Test - public void processEvent_loginDetected() throws Exception { + public void processEvent_loginDetected_true_eventText() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer).clear(); - verify(mMockEventBuffer).toArray(); - assertOnLoginDetected(); - } - - @Test - public void processEvent_loginDetected_passwordFieldNotDetected() { - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } - - @Test - public void processEvent_loginDetected_suspiciousTextNotDetected() { - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } - - @Test - public void processEvent_loginDetected_withoutViewNode() { - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ TEXT_REQUIRED1, + /* viewNodeText= */ null, + /* hintText= */ null)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ TEXT_REQUIRED2, + /* viewNodeText= */ null, + /* hintText= */ null)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ TEXT_OPTIONAL1, + /* viewNodeText= */ null, + /* hintText= */ null)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginDetected(); } @Test - public void processEvent_loginDetected_belowResetLimit() throws Exception { + public void processEvent_loginDetected_true_viewNodeText() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); - - for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; i++) { - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - } - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ TEXT_REQUIRED1, + /* hintText= */ null)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ TEXT_REQUIRED2, + /* hintText= */ null)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ TEXT_OPTIONAL1, + /* hintText= */ null)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer).clear(); - verify(mMockEventBuffer).toArray(); - assertOnLoginDetected(); + assertLoginDetected(); } @Test - public void processEvent_loginDetected_aboveResetLimit() throws Exception { - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); - - for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS + 1; i++) { - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - } - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); + public void processEvent_loginDetected_true_hintText() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ null, + /* hintText= */ TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ null, + /* hintText= */ TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent( + createProcessEvent( + /* eventText= */ null, + /* viewNodeText= */ null, + /* hintText= */ TEXT_OPTIONAL1)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); + assertLoginDetected(); } @Test - public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception { + public void processEvent_loginDetected_true_differentOptionalGroup() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL2)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer).clear(); - verify(mMockEventBuffer).toArray(); - assertOnLoginDetected(); + assertLoginDetected(); } @Test - public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception { + public void processEvent_loginDetected_true_usesContains() throws Exception { when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_CONTAINS_OPTIONAL3)); - mContentProtectionEventProcessor.mPasswordFieldDetected = true; - mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, times(2)).clear(); - verify(mMockEventBuffer, times(2)).toArray(); - assertOnLoginDetected(PROCESS_EVENT, /* times= */ 2); + assertLoginDetected(); } @Test - public void isPasswordField_android() { - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); - - mContentProtectionEventProcessor.processEvent(event); + public void processEvent_loginDetected_false_missingRequiredGroups() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_android_withoutClassName() { - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - /* className= */ null, InputType.TYPE_TEXT_VARIATION_PASSWORD); - - mContentProtectionEventProcessor.processEvent(event); + public void processEvent_loginDetected_false_missingOptionalGroups() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_android_wrongClassName() { - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - "wrong.prefix" + ANDROID_CLASS_NAME, - InputType.TYPE_TEXT_VARIATION_PASSWORD); + public void processEvent_loginDetected_false_safeText() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE)); - mContentProtectionEventProcessor.processEvent(event); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_android_wrongInputType() { - ContentCaptureEvent event = - createAndroidPasswordFieldEvent( - ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_NORMAL); + public void processEvent_loginDetected_false_sharedTextOnce() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED)); - mContentProtectionEventProcessor.processEvent(event); - - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_webView() throws Exception { - ContentCaptureEvent event = - createWebViewPasswordFieldEvent( - /* className= */ null, /* eventText= */ null, PASSWORD_TEXT); - when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[] {event}); + public void processEvent_loginDetected_true_sharedTextMultiple() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer).clear(); - verify(mMockEventBuffer).toArray(); - assertOnLoginDetected(event, /* times= */ 1); + assertLoginDetected(); } @Test - public void isPasswordField_webView_withClassName() { - ContentCaptureEvent event = - createWebViewPasswordFieldEvent( - /* className= */ "any.class.name", /* eventText= */ null, PASSWORD_TEXT); + public void processEvent_loginDetected_false_inspectsOnlyTypeViewAppeared() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); - mContentProtectionEventProcessor.processEvent(event); + for (int type = -100; type <= 100; type++) { + if (type == TYPE_VIEW_APPEARED) { + continue; + } + ContentCaptureEvent event = createEvent(type); + event.setText(TEXT_OPTIONAL1); + mContentProtectionEventProcessor.processEvent(event); + } - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isPasswordField_webView_withSafeViewNodeText() { - ContentCaptureEvent event = - createWebViewPasswordFieldEvent( - /* className= */ null, /* eventText= */ null, SAFE_TEXT); + public void processEvent_loginDetected_true_belowResetLimit() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } + for (int i = 0; i < BUFFER_SIZE - 2; i++) { + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + } - @Test - public void isPasswordField_webView_withEventText() { - ContentCaptureEvent event = - createWebViewPasswordFieldEvent(/* className= */ null, PASSWORD_TEXT, SAFE_TEXT); + assertLoginNotDetected(); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); - assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginDetected(); } @Test - public void isSuspiciousText_withSafeText() { - ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SAFE_TEXT); + public void processEvent_loginDetected_false_aboveResetLimit() { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); - mContentProtectionEventProcessor.processEvent(event); - - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } + for (int i = 0; i < BUFFER_SIZE - 1; i++) { + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + } - @Test - public void isSuspiciousText_eventText_suspiciousText() { - ContentCaptureEvent event = createSuspiciousTextEvent(SUSPICIOUS_TEXT, SAFE_TEXT); + assertLoginNotDetected(); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginNotDetected(); } @Test - public void isSuspiciousText_viewNodeText_suspiciousText() { - ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SUSPICIOUS_TEXT); + public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - mContentProtectionEventProcessor.processEvent(event); + for (int i = 0; i < 2; i++) { + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); + } - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginDetected(); } @Test - public void isSuspiciousText_eventText_passwordText() { - ContentCaptureEvent event = createSuspiciousTextEvent(PASSWORD_TEXT, SAFE_TEXT); - - mContentProtectionEventProcessor.processEvent(event); + public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception { + when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); - } + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - @Test - public void isSuspiciousText_viewNodeText_passwordText() { - // Specify the class to differ from {@link isPasswordField_webView} test in this version - ContentCaptureEvent event = - createProcessEvent( - "test.class.not.a.web.view", /* inputType= */ 0, SAFE_TEXT, PASSWORD_TEXT); + mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5); - mContentProtectionEventProcessor.processEvent(event); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); + mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); + mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); - verify(mMockEventBuffer, never()).clear(); - verify(mMockEventBuffer, never()).toArray(); - verifyZeroInteractions(mMockContentCaptureManager); + assertLoginDetected(times(2)); } private static ContentCaptureEvent createEvent(int type) { @@ -511,20 +415,20 @@ public class ContentProtectionEventProcessorTest { return createEvent(TYPE_VIEW_APPEARED); } + private ContentCaptureEvent createProcessEvent(@Nullable String eventText) { + return createProcessEvent(eventText, /* viewNodeText= */ null, /* hintText= */ null); + } + private ContentCaptureEvent createProcessEvent( - @Nullable String className, - int inputType, - @Nullable String eventText, - @Nullable String viewNodeText) { + @Nullable String eventText, @Nullable String viewNodeText, @Nullable String hintText) { View view = new View(mContext); ViewStructureImpl viewStructure = new ViewStructureImpl(view); - if (className != null) { - viewStructure.setClassName(className); - } if (viewNodeText != null) { viewStructure.setText(viewNodeText); } - viewStructure.setInputType(inputType); + if (hintText != null) { + viewStructure.setHint(hintText); + } ContentCaptureEvent event = createProcessEvent(); event.setViewNode(viewStructure.getNode()); @@ -535,34 +439,28 @@ public class ContentProtectionEventProcessorTest { return event; } - private ContentCaptureEvent createAndroidPasswordFieldEvent( - @Nullable String className, int inputType) { - return createProcessEvent( - className, inputType, /* eventText= */ null, /* viewNodeText= */ null); - } - - private ContentCaptureEvent createWebViewPasswordFieldEvent( - @Nullable String className, @Nullable String eventText, @Nullable String viewNodeText) { - return createProcessEvent(className, /* inputType= */ 0, eventText, viewNodeText); + private void assertLoginNotDetected() { + mTestLooper.dispatchAll(); + verify(mMockEventBuffer, never()).clear(); + verify(mMockEventBuffer, never()).toArray(); + verifyZeroInteractions(mMockContentCaptureManager); } - private ContentCaptureEvent createSuspiciousTextEvent( - @Nullable String eventText, @Nullable String viewNodeText) { - return createProcessEvent( - /* className= */ null, /* inputType= */ 0, eventText, viewNodeText); + private void assertLoginDetected() throws Exception { + assertLoginDetected(times(1)); } - private void assertOnLoginDetected() throws Exception { - assertOnLoginDetected(PROCESS_EVENT, /* times= */ 1); - } + private void assertLoginDetected(@NonNull VerificationMode verificationMode) throws Exception { + mTestLooper.dispatchAll(); + verify(mMockEventBuffer, verificationMode).clear(); + verify(mMockEventBuffer, verificationMode).toArray(); - private void assertOnLoginDetected(ContentCaptureEvent event, int times) throws Exception { ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class); - verify(mMockContentCaptureManager, times(times)).onLoginDetected(captor.capture()); + verify(mMockContentCaptureManager, verificationMode).onLoginDetected(captor.capture()); assertThat(captor.getValue()).isNotNull(); List<ContentCaptureEvent> actual = captor.getValue().getList(); assertThat(actual).isNotNull(); - assertThat(actual).containsExactly(event); + assertThat(actual).containsExactly(PROCESS_EVENT); } } diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java index 1459799adee5..fbe478e31888 100644 --- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java +++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * 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. @@ -44,68 +44,74 @@ public class ContentProtectionUtilsTest { private static final String TEXT = "TEST_TEXT"; - private static final ContentCaptureEvent EVENT = createEvent(); - - private static final ViewNode VIEW_NODE = new ViewNode(); - - private static final ViewNode VIEW_NODE_WITH_TEXT = createViewNodeWithText(); + private static final String TEXT_LOWER = TEXT.toLowerCase(); @Test - public void event_getEventText_null() { - String actual = ContentProtectionUtils.getEventText(EVENT); + public void getEventTextLower_null() { + String actual = ContentProtectionUtils.getEventTextLower(createEvent()); assertThat(actual).isNull(); } @Test - public void event_getEventText_notNull() { - ContentCaptureEvent event = createEvent(); - event.setText(TEXT); - - String actual = ContentProtectionUtils.getEventText(event); + public void getEventTextLower_notNull() { + String actual = ContentProtectionUtils.getEventTextLower(createEventWithText()); - assertThat(actual).isEqualTo(TEXT); + assertThat(actual).isEqualTo(TEXT_LOWER); } @Test - public void event_getViewNodeText_null() { - String actual = ContentProtectionUtils.getViewNodeText(EVENT); + public void getViewNodeTextLower_null() { + String actual = ContentProtectionUtils.getViewNodeTextLower(new ViewNode()); assertThat(actual).isNull(); } @Test - public void event_getViewNodeText_notNull() { - ContentCaptureEvent event = createEvent(); - event.setViewNode(VIEW_NODE_WITH_TEXT); - - String actual = ContentProtectionUtils.getViewNodeText(event); + public void getViewNodeTextLower_notNull() { + String actual = ContentProtectionUtils.getViewNodeTextLower(createViewNodeWithText()); - assertThat(actual).isEqualTo(TEXT); + assertThat(actual).isEqualTo(TEXT_LOWER); } @Test - public void viewNode_getViewNodeText_null() { - String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE); + public void getHintTextLower_null() { + String actual = ContentProtectionUtils.getHintTextLower(new ViewNode()); assertThat(actual).isNull(); } @Test - public void viewNode_getViewNodeText_notNull() { - String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE_WITH_TEXT); + public void getHintTextLower_notNull() { + String actual = ContentProtectionUtils.getHintTextLower(createViewNodeWithHint()); - assertThat(actual).isEqualTo(TEXT); + assertThat(actual).isEqualTo(TEXT_LOWER); } private static ContentCaptureEvent createEvent() { return new ContentCaptureEvent(/* sessionId= */ 123, TYPE_SESSION_STARTED); } - private static ViewNode createViewNodeWithText() { + private static ContentCaptureEvent createEventWithText() { + ContentCaptureEvent event = createEvent(); + event.setText(TEXT); + return event; + } + + private static ViewStructureImpl createViewStructureImpl() { View view = new View(ApplicationProvider.getApplicationContext()); - ViewStructureImpl viewStructure = new ViewStructureImpl(view); - viewStructure.setText(TEXT); - return viewStructure.getNode(); + return new ViewStructureImpl(view); + } + + private static ViewNode createViewNodeWithText() { + ViewStructureImpl viewStructureImpl = createViewStructureImpl(); + viewStructureImpl.setText(TEXT); + return viewStructureImpl.getNode(); + } + + private static ViewNode createViewNodeWithHint() { + ViewStructureImpl viewStructureImpl = createViewStructureImpl(); + viewStructureImpl.setHint(TEXT); + return viewStructureImpl.getNode(); } } diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java index 31c2eb2efaed..b7ea04fdfe07 100644 --- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java +++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java @@ -128,25 +128,6 @@ public class AndroidKeyStoreMaintenance { } /** - * Queries user state from Keystore 2.0. - * - * @param userId - Android user id of the user. - * @return UserState enum variant as integer if successful or an error - */ - public static int getState(int userId) { - StrictMode.noteDiskRead(); - try { - return getService().getState(userId); - } catch (ServiceSpecificException e) { - Log.e(TAG, "getState failed", e); - return e.errorCode; - } catch (Exception e) { - Log.e(TAG, "Can not connect to keystore", e); - return SYSTEM_ERROR; - } - } - - /** * Informs Keystore 2.0 that an off body event was detected. */ public static void onDeviceOffBody() { diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 8045f55f6b4c..11b827117aa3 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -19,8 +19,6 @@ package android.security; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.StrictMode; -import android.os.UserHandle; -import android.security.maintenance.UserState; /** * @hide This should not be made public in its present form because it @@ -37,15 +35,6 @@ public class KeyStore { // Used for UID field to indicate the calling UID. public static final int UID_SELF = -1; - // States - public enum State { - @UnsupportedAppUsage - UNLOCKED, - @UnsupportedAppUsage - LOCKED, - UNINITIALIZED - }; - private static final KeyStore KEY_STORE = new KeyStore(); @UnsupportedAppUsage @@ -55,28 +44,6 @@ public class KeyStore { /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public State state(int userId) { - int userState = AndroidKeyStoreMaintenance.getState(userId); - switch (userState) { - case UserState.UNINITIALIZED: - return KeyStore.State.UNINITIALIZED; - case UserState.LSKF_UNLOCKED: - return KeyStore.State.UNLOCKED; - case UserState.LSKF_LOCKED: - return KeyStore.State.LOCKED; - default: - throw new AssertionError(userState); - } - } - - /** @hide */ - @UnsupportedAppUsage - public State state() { - return state(UserHandle.myUserId()); - } - - /** @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public byte[] get(String key) { return null; } diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 96c257b304a0..1ba41b106f56 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -75,16 +75,18 @@ import javax.security.auth.x500.X500Principal; * {@link java.security.interfaces.ECPublicKey} or {@link java.security.interfaces.RSAPublicKey} * interfaces. * - * <p>For asymmetric key pairs, a self-signed X.509 certificate will be also generated and stored in - * the Android Keystore. This is because the {@link java.security.KeyStore} abstraction does not - * support storing key pairs without a certificate. The subject, serial number, and validity dates - * of the certificate can be customized in this spec. The self-signed certificate may be replaced at - * a later time by a certificate signed by a Certificate Authority (CA). + * <p>For asymmetric key pairs, a X.509 certificate will be also generated and stored in the Android + * Keystore. This is because the {@link java.security.KeyStore} abstraction does not support storing + * key pairs without a certificate. The subject, serial number, and validity dates of the + * certificate can be customized in this spec. The certificate may be replaced at a later time by a + * certificate signed by a Certificate Authority (CA). * - * <p>NOTE: If a private key is not authorized to sign the self-signed certificate, then the - * certificate will be created with an invalid signature which will not verify. Such a certificate - * is still useful because it provides access to the public key. To generate a valid signature for - * the certificate the key needs to be authorized for all of the following: + * <p>NOTE: If attestation is not requested using {@link Builder#setAttestationChallenge(byte[])}, + * generated certificate may be self-signed. If a private key is not authorized to sign the + * certificate, then the certificate will be created with an invalid signature which will not + * verify. Such a certificate is still useful because it provides access to the public key. To + * generate a valid signature for the certificate the key needs to be authorized for all of the + * following: * <ul> * <li>{@link KeyProperties#PURPOSE_SIGN},</li> * <li>operation without requiring the user to be authenticated (see @@ -989,12 +991,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be * used. Attempts to use the key for any other purpose will be rejected. * - * <p>If the set of purposes for which the key can be used does not contain - * {@link KeyProperties#PURPOSE_SIGN}, the self-signed certificate generated by - * {@link KeyPairGenerator} of {@code AndroidKeyStore} provider will contain an - * invalid signature. This is OK if the certificate is only used for obtaining the - * public key from Android KeyStore. - * * <p>See {@link KeyProperties}.{@code PURPOSE} flags. */ public Builder(@NonNull String keystoreAlias, @KeyProperties.PurposeEnum int purposes) { @@ -1140,7 +1136,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** - * Sets the subject used for the self-signed certificate of the generated key pair. + * Sets the subject used for the certificate of the generated key pair. * * <p>By default, the subject is {@code CN=fake}. */ @@ -1154,7 +1150,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** - * Sets the serial number used for the self-signed certificate of the generated key pair. + * Sets the serial number used for the certificate of the generated key pair. * * <p>By default, the serial number is {@code 1}. */ @@ -1168,8 +1164,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** - * Sets the start of the validity period for the self-signed certificate of the generated - * key pair. + * Sets the start of the validity period for the certificate of the generated key pair. * * <p>By default, this date is {@code Jan 1 1970}. */ @@ -1183,8 +1178,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** - * Sets the end of the validity period for the self-signed certificate of the generated key - * pair. + * Sets the end of the validity period for the certificate of the generated key pair. * * <p>By default, this date is {@code Jan 1 2048}. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index 06ce37148eaf..8cf869b175ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -87,33 +87,28 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle mTransitions.addHandler(this); } - @Override - public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - boolean containsEmbeddingSplit = false; - boolean containsNonEmbeddedChange = false; - final List<TransitionInfo.Change> changes = info.getChanges(); - for (int i = changes.size() - 1; i >= 0; i--) { - final TransitionInfo.Change change = changes.get(i); - if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { - containsNonEmbeddedChange = true; - } else if (!change.hasFlags(FLAG_FILLS_TASK)) { + /** Whether ActivityEmbeddingController should animate this transition. */ + public boolean shouldAnimate(@NonNull TransitionInfo info) { + boolean containsEmbeddingChange = false; + for (TransitionInfo.Change change : info.getChanges()) { + if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags( + FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { // Whether the Task contains any ActivityEmbedding split before or after the // transition. - containsEmbeddingSplit = true; + containsEmbeddingChange = true; } } - if (!containsEmbeddingSplit) { + if (!containsEmbeddingChange) { // Let the system to play the default animation if there is no ActivityEmbedding split // window. This allows to play the app customized animation when there is no embedding, // such as the device is in a folded state. return false; } - if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) { + + if (containsNonEmbeddedChange(info) && !handleNonEmbeddedChanges(info.getChanges())) { return false; } + final TransitionInfo.AnimationOptions options = info.getAnimationOptions(); if (options != null // Scene-transition will be handled by app side. @@ -123,6 +118,17 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle return false; } + return true; + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + + if (!shouldAnimate(info)) return false; + // Start ActivityEmbedding animation. mTransitionCallbacks.put(transition, finishCallback); mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction); @@ -136,6 +142,16 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle mAnimationRunner.cancelAnimationFromMerge(); } + /** Whether TransitionInfo contains non-ActivityEmbedding embedded window. */ + private boolean containsNonEmbeddedChange(@NonNull TransitionInfo info) { + for (TransitionInfo.Change change : info.getChanges()) { + if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { + return true; + } + } + return false; + } + private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) { final Rect nonClosingEmbeddedArea = new Rect(); for (int i = changes.size() - 1; i >= 0; i--) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 14a040a40874..a533ca5fa8fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -32,6 +32,7 @@ import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.BubbleData; import com.android.wm.shell.bubbles.BubbleDataRepository; @@ -366,11 +367,12 @@ public abstract class WMShellModule { KeyguardTransitionHandler keyguardTransitionHandler, Optional<DesktopTasksController> desktopTasksController, Optional<UnfoldTransitionHandler> unfoldHandler, + Optional<ActivityEmbeddingController> activityEmbeddingController, Transitions transitions) { return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional, pipTransitionController, recentsTransitionHandler, keyguardTransitionHandler, desktopTasksController, - unfoldHandler); + unfoldHandler, activityEmbeddingController); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index d31476c63890..d277eef761e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -925,19 +925,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { if (toHome) wct.reorder(mRecentsTask, true /* toTop */); else wct.restoreTransientOrder(mRecentsTask); } - if (!toHome - // If a recents gesture starts on the 3p launcher, then the 3p launcher is the - // live tile (pausing app). If the gesture is "cancelled" we need to return to - // 3p launcher instead of "task-switching" away from it. - && (!mWillFinishToHome || mPausingSeparateHome) - && mPausingTasks != null && mState == STATE_NORMAL) { - if (mPausingSeparateHome) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, - " returning to 3p home"); - } else { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, - " returning to app"); - } + if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app"); // The gesture is returning to the pausing-task(s) rather than continuing with // recents, so end the transition by moving the app back to the top (and also // re-showing it's task). @@ -969,6 +958,15 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { wct.restoreTransientOrder(mRecentsTask); } } else { + if (mPausingSeparateHome) { + if (mOpeningTasks.isEmpty()) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " recents occluded 3p home"); + } else { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " switch task by recents on 3p home"); + } + } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " normal finish"); // The general case: committing to recents, going home, or switching tasks. for (int i = 0; i < mOpeningTasks.size(); ++i) { 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 68ca2313f709..7a4834cb5adb 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 @@ -23,6 +23,7 @@ import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVIT import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; @@ -395,6 +396,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return mMainStage.isActive(); } + /** @return whether this transition-request has the launch-adjacent flag. */ + public boolean requestHasLaunchAdjacentFlag(TransitionRequestInfo request) { + final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); + return triggerTask != null && triggerTask.baseIntent != null + && (triggerTask.baseIntent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0; + } + /** @return whether the transition-request implies entering pip from split. */ public boolean requestImpliesSplitToPip(TransitionRequestInfo request) { if (!isSplitActive() || !mMixedHandler.requestHasPipEnter(request)) { @@ -2459,10 +2467,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); } - // When split in the background, it should be only opening/dismissing transition and - // would keep out not empty. Prevent intercepting all transitions for split screen when - // it is in the background and not identify to handle it. - return (!out.isEmpty() || isSplitScreenVisible()) ? out : null; + if (!out.isEmpty()) { + // One of the cases above handled it + return out; + } else if (isSplitScreenVisible()) { + // If split is visible, only defer handling this transition if it's launching + // adjacent while there is already a split pair -- this may trigger PIP and + // that should be handled by the mixed handler. + final boolean deferTransition = requestHasLaunchAdjacentFlag(request) + && mMainStage.getChildCount() != 0 && mSideStage.getChildCount() != 0; + return !deferTransition ? out : null; + } + // Don't intercept the transition if we are not handling it as a part of one of the + // cases above and it is not already visible + return null; } else { if (isOpening && getStageOfTask(triggerTask) != null) { // One task is appearing into split, prepare to enter split screen. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 451e61855943..918a5a4bd53e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -21,7 +21,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; @@ -43,6 +45,7 @@ import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; @@ -74,6 +77,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, private final KeyguardTransitionHandler mKeyguardHandler; private DesktopTasksController mDesktopTasksController; private UnfoldTransitionHandler mUnfoldHandler; + private ActivityEmbeddingController mActivityEmbeddingController; private static class MixedTransition { static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; @@ -93,9 +97,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, /** Recents Transition while in desktop mode. */ static final int TYPE_RECENTS_DURING_DESKTOP = 6; - /** Fuld/Unfold transition. */ + /** Fold/Unfold transition. */ static final int TYPE_UNFOLD = 7; + /** Enter pip from one of the Activity Embedding windows. */ + static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 8; + /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; @@ -150,7 +157,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, Optional<RecentsTransitionHandler> recentsHandlerOptional, KeyguardTransitionHandler keyguardHandler, Optional<DesktopTasksController> desktopTasksControllerOptional, - Optional<UnfoldTransitionHandler> unfoldHandler) { + Optional<UnfoldTransitionHandler> unfoldHandler, + Optional<ActivityEmbeddingController> activityEmbeddingController) { mPlayer = player; mKeyguardHandler = keyguardHandler; if (Transitions.ENABLE_SHELL_TRANSITIONS @@ -170,6 +178,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } mDesktopTasksController = desktopTasksControllerOptional.orElse(null); mUnfoldHandler = unfoldHandler.orElse(null); + mActivityEmbeddingController = activityEmbeddingController.orElse(null); }, this); } } @@ -192,6 +201,16 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mPipHandler.augmentRequest(transition, request, out); mSplitHandler.addEnterOrExitIfNeeded(request, out); return out; + } else if (request.getType() == TRANSIT_PIP + && (request.getFlags() & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0 && ( + mActivityEmbeddingController != null)) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + " Got a PiP-enter request from an Activity Embedding split"); + mActiveTransitions.add(new MixedTransition( + MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition)); + // Postpone transition splitting to later. + WindowContainerTransaction out = new WindowContainerTransaction(); + return out; } else if (request.getRemoteTransition() != null && TransitionUtil.isOpeningType(request.getType()) && (request.getTriggerTask() == null @@ -355,6 +374,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction, finishCallback); + } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { + return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction, + finishTransaction, finishCallback); } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) { return false; } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { @@ -400,6 +422,58 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } } + private boolean animateEnterPipFromActivityEmbedding(@NonNull MixedTransition mixed, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " + + "entering PIP from an Activity Embedding window"); + // Split into two transitions (wct) + TransitionInfo.Change pipChange = null; + final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (mPipHandler.isEnteringPip(change, info.getType())) { + if (pipChange != null) { + throw new IllegalStateException("More than 1 pip-entering changes in one" + + " transition? " + info); + } + pipChange = change; + // going backwards, so remove-by-index is fine. + everythingElse.getChanges().remove(i); + } + } + + final Transitions.TransitionFinishCallback finishCB = (wct) -> { + --mixed.mInFlightSubAnimations; + mixed.joinFinishArgs(wct); + if (mixed.mInFlightSubAnimations > 0) return; + mActiveTransitions.remove(mixed); + finishCallback.onTransitionFinished(mixed.mFinishWCT); + }; + + if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) { + // Fallback to dispatching to other handlers. + return false; + } + + // PIP window should always be on the highest Z order. + if (pipChange != null) { + mixed.mInFlightSubAnimations = 2; + mPipHandler.startEnterAnimation( + pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE), + finishTransaction, + finishCB); + } else { + mixed.mInFlightSubAnimations = 1; + } + + mActivityEmbeddingController.startAnimation(mixed.mTransition, everythingElse, + startTransaction, finishTransaction, finishCB); + return true; + } + private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -811,6 +885,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } else { mPipHandler.end(); } + } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { + mPipHandler.end(); + mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget, + finishCallback); } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { mPipHandler.end(); if (mixed.mLeftoversHandler != null) { @@ -851,6 +929,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (mixed == null) return; if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { mPipHandler.onTransitionConsumed(transition, aborted, finishT); + } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { + mPipHandler.onTransitionConsumed(transition, aborted, finishT); + mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { diff --git a/media/java/android/media/RingtoneV1.java b/media/java/android/media/RingtoneV1.java index b761afaeaa67..3c54d4a0d166 100644 --- a/media/java/android/media/RingtoneV1.java +++ b/media/java/android/media/RingtoneV1.java @@ -16,14 +16,15 @@ package android.media; -import android.annotation.NonNull; import android.annotation.Nullable; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.Resources.NotFoundException; import android.media.audiofx.HapticGenerator; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.RemoteException; import android.os.Trace; import android.os.VibrationEffect; @@ -61,7 +62,6 @@ class RingtoneV1 implements Ringtone.ApiInterface { private final Context mContext; private final AudioManager mAudioManager; - private final Ringtone.Injectables mInjectables; private VolumeShaper.Configuration mVolumeShaperConfig; private VolumeShaper mVolumeShaper; @@ -74,10 +74,12 @@ class RingtoneV1 implements Ringtone.ApiInterface { private final IRingtonePlayer mRemotePlayer; private final Binder mRemoteToken; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private MediaPlayer mLocalPlayer; private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener(); private HapticGenerator mHapticGenerator; + @UnsupportedAppUsage private Uri mUri; private String mTitle; @@ -92,15 +94,10 @@ class RingtoneV1 implements Ringtone.ApiInterface { private boolean mHapticGeneratorEnabled = false; private final Object mPlaybackSettingsLock = new Object(); - /** @hide */ + /** {@hide} */ + @UnsupportedAppUsage public RingtoneV1(Context context, boolean allowRemote) { - this(context, new Ringtone.Injectables(), allowRemote); - } - - /** @hide */ - RingtoneV1(Context context, @NonNull Ringtone.Injectables injectables, boolean allowRemote) { mContext = context; - mInjectables = injectables; mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); mAllowRemote = allowRemote; mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null; @@ -203,7 +200,7 @@ class RingtoneV1 implements Ringtone.ApiInterface { } destroyLocalPlayer(); // try opening uri locally before delegating to remote player - mLocalPlayer = mInjectables.newMediaPlayer(); + mLocalPlayer = new MediaPlayer(); try { mLocalPlayer.setDataSource(mContext, mUri); mLocalPlayer.setAudioAttributes(mAudioAttributes); @@ -243,7 +240,19 @@ class RingtoneV1 implements Ringtone.ApiInterface { */ public boolean hasHapticChannels() { // FIXME: support remote player, or internalize haptic channels support and remove entirely. - return mInjectables.hasHapticChannels(mLocalPlayer); + try { + android.os.Trace.beginSection("Ringtone.hasHapticChannels"); + if (mLocalPlayer != null) { + for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) { + if (trackInfo.hasHapticChannels()) { + return true; + } + } + } + } finally { + android.os.Trace.endSection(); + } + return false; } /** @@ -325,7 +334,7 @@ class RingtoneV1 implements Ringtone.ApiInterface { * @see android.media.audiofx.HapticGenerator#isAvailable() */ public boolean setHapticGeneratorEnabled(boolean enabled) { - if (!mInjectables.isHapticGeneratorAvailable()) { + if (!HapticGenerator.isAvailable()) { return false; } synchronized (mPlaybackSettingsLock) { @@ -353,7 +362,7 @@ class RingtoneV1 implements Ringtone.ApiInterface { mLocalPlayer.setVolume(mVolume); mLocalPlayer.setLooping(mIsLooping); if (mHapticGenerator == null && mHapticGeneratorEnabled) { - mHapticGenerator = mInjectables.createHapticGenerator(mLocalPlayer); + mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId()); } if (mHapticGenerator != null) { mHapticGenerator.setEnabled(mHapticGeneratorEnabled); @@ -388,6 +397,7 @@ class RingtoneV1 implements Ringtone.ApiInterface { * * @hide */ + @UnsupportedAppUsage public void setUri(Uri uri) { setUri(uri, null); } @@ -415,6 +425,7 @@ class RingtoneV1 implements Ringtone.ApiInterface { } /** {@hide} */ + @UnsupportedAppUsage public Uri getUri() { return mUri; } @@ -545,7 +556,7 @@ class RingtoneV1 implements Ringtone.ApiInterface { Log.e(TAG, "Could not load fallback ringtone"); return false; } - mLocalPlayer = mInjectables.newMediaPlayer(); + mLocalPlayer = new MediaPlayer(); if (afd.getDeclaredLength() < 0) { mLocalPlayer.setDataSource(afd.getFileDescriptor()); } else { @@ -583,12 +594,12 @@ class RingtoneV1 implements Ringtone.ApiInterface { } public boolean isLocalOnly() { - return !mAllowRemote; + return mAllowRemote; } public boolean isUsingRemotePlayer() { // V2 testing api, but this is the v1 approximation. - return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null) && (mUri != null); + return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null); } class MyOnCompletionListener implements MediaPlayer.OnCompletionListener { diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java index bbbe7f683b05..c54bfce840aa 100644 --- a/media/java/android/media/midi/MidiUmpDeviceService.java +++ b/media/java/android/media/midi/MidiUmpDeviceService.java @@ -16,6 +16,9 @@ package android.media.midi; +import static com.android.media.midi.flags.Flags.FLAG_VIRTUAL_UMP; + +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Service; @@ -54,9 +57,11 @@ import java.util.List; * android:resource="@xml/device_info" /> * </service></pre> */ +@FlaggedApi(FLAG_VIRTUAL_UMP) public abstract class MidiUmpDeviceService extends Service { private static final String TAG = "MidiUmpDeviceService"; + @FlaggedApi(FLAG_VIRTUAL_UMP) public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService"; private IMidiManager mMidiManager; @@ -75,6 +80,7 @@ public abstract class MidiUmpDeviceService extends Service { } }; + @FlaggedApi(FLAG_VIRTUAL_UMP) @Override public void onCreate() { mMidiManager = IMidiManager.Stub.asInterface( @@ -112,6 +118,7 @@ public abstract class MidiUmpDeviceService extends Service { * The number of input and output ports must be equal and non-zero. * @return list of MidiReceivers */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public abstract @NonNull List<MidiReceiver> onGetInputPortReceivers(); /** @@ -120,6 +127,7 @@ public abstract class MidiUmpDeviceService extends Service { * The number of input and output ports must be equal and non-zero. * @return the list of MidiReceivers */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public final @NonNull List<MidiReceiver> getOutputPortReceivers() { if (mServer == null) { return new ArrayList<MidiReceiver>(); @@ -132,6 +140,7 @@ public abstract class MidiUmpDeviceService extends Service { * Returns the {@link MidiDeviceInfo} instance for this service * @return the MidiDeviceInfo of the virtual MIDI device if it was successfully created */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public final @Nullable MidiDeviceInfo getDeviceInfo() { return mDeviceInfo; } @@ -140,6 +149,7 @@ public abstract class MidiUmpDeviceService extends Service { * Called to notify when the {@link MidiDeviceStatus} has changed * @param status the current status of the MIDI device */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public void onDeviceStatusChanged(@NonNull MidiDeviceStatus status) { } @@ -147,9 +157,11 @@ public abstract class MidiUmpDeviceService extends Service { * Called to notify when the virtual MIDI device running in this service has been closed by * all its clients */ + @FlaggedApi(FLAG_VIRTUAL_UMP) public void onClose() { } + @FlaggedApi(FLAG_VIRTUAL_UMP) @Override public @Nullable IBinder onBind(@NonNull Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) { diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl index 80e22477efed..31e65eb13926 100644 --- a/media/java/android/media/projection/IMediaProjectionManager.aidl +++ b/media/java/android/media/projection/IMediaProjectionManager.aidl @@ -175,5 +175,5 @@ interface IMediaProjectionManager { @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION") @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") - void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource); + oneway void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource); } diff --git a/media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java index 2c8daba86d19..3c0c6847f557 100644 --- a/media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java @@ -14,22 +14,20 @@ * limitations under the License. */ -package com.android.media; +package com.android.mediaframeworktest.unit; import static android.media.Ringtone.MEDIA_SOUND; import static android.media.Ringtone.MEDIA_SOUND_AND_VIBRATION; import static android.media.Ringtone.MEDIA_VIBRATION; -import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerFallbackSetup; -import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerSetup; -import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerStarted; -import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerStopped; - import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; @@ -55,29 +53,34 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.testing.TestableContext; +import android.util.ArrayMap; +import android.util.ArraySet; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; -import com.android.framework.base.media.ringtone.tests.R; -import com.android.media.testing.RingtoneInjectablesTrackingTestRule; +import com.android.mediaframeworktest.R; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.Description; import org.junit.runner.RunWith; +import org.junit.runners.model.Statement; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.io.FileNotFoundException; +import java.util.ArrayDeque; +import java.util.Map; +import java.util.Queue; -/** - * Test behavior of {@link Ringtone} when it's created via {@link Ringtone.Builder}. - */ @RunWith(AndroidJUnit4.class) -public class RingtoneBuilderTest { +public class RingtoneTest { private static final Uri SOUND_URI = Uri.parse("content://fake-sound-uri"); @@ -90,8 +93,11 @@ public class RingtoneBuilderTest { private static final VibrationEffect VIBRATION_EFFECT = VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100}, -1); + private static final VibrationEffect VIBRATION_EFFECT_REPEATING = + VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100, 50}, 1); - @Rule public final RingtoneInjectablesTrackingTestRule + @Rule + public final RingtoneInjectablesTrackingTestRule mMediaPlayerRule = new RingtoneInjectablesTrackingTestRule(); @Captor private ArgumentCaptor<IBinder> mIBinderCaptor; @@ -116,7 +122,6 @@ public class RingtoneBuilderTest { mContext = spy(testContext); } - @Test public void testRingtone_fullLifecycleUsingLocalMediaPlayer() throws Exception { MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer(); @@ -137,14 +142,14 @@ public class RingtoneBuilderTest { assertThat(ringtone.isLocalOnly()).isFalse(); // Prepare - verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES); + verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES); verify(mockMediaPlayer).setVolume(1.0f); verify(mockMediaPlayer).setLooping(false); verify(mockMediaPlayer).prepare(); // Play ringtone.play(); - verifyPlayerStarted(mockMediaPlayer); + verifyLocalPlay(mockMediaPlayer); // Verify dynamic controls. ringtone.setVolume(0.8f); @@ -160,7 +165,7 @@ public class RingtoneBuilderTest { // Release ringtone.stop(); - verifyPlayerStopped(mockMediaPlayer); + verifyLocalStop(mockMediaPlayer); // This test is intended to strictly verify all interactions with MediaPlayer in a local // playback case. This shouldn't be necessary in other tests that have the same basic @@ -194,16 +199,16 @@ public class RingtoneBuilderTest { assertThat(ringtone.getAudioAttributes()).isEqualTo(audioAttributes); // Prepare - verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, audioAttributes); + verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, audioAttributes); verify(mockMediaPlayer).prepare(); // Play ringtone.play(); - verifyPlayerStarted(mockMediaPlayer); + verifyLocalPlay(mockMediaPlayer); // Release ringtone.stop(); - verifyPlayerStopped(mockMediaPlayer); + verifyLocalStop(mockMediaPlayer); verifyZeroInteractions(mMockRemotePlayer); verifyZeroInteractions(mMockVibrator); @@ -215,8 +220,8 @@ public class RingtoneBuilderTest { setupFileNotFound(mockMediaPlayer, SOUND_URI); Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES) - .setUri(SOUND_URI) - .build(); + .setUri(SOUND_URI) + .build(); assertThat(ringtone).isNotNull(); assertThat(ringtone.isUsingRemotePlayer()).isTrue(); @@ -279,7 +284,7 @@ public class RingtoneBuilderTest { // Prepare // Uses attributes with haptic channels enabled, but will use the effect when there aren't // any present. - verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); + verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); verify(mockMediaPlayer).setVolume(1.0f); verify(mockMediaPlayer).setLooping(false); verify(mockMediaPlayer).prepare(); @@ -287,7 +292,7 @@ public class RingtoneBuilderTest { // Play ringtone.play(); - verifyPlayerStarted(mockMediaPlayer); + verifyLocalPlay(mockMediaPlayer); verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES); // Verify dynamic controls. @@ -305,7 +310,7 @@ public class RingtoneBuilderTest { // Release ringtone.stop(); - verifyPlayerStopped(mockMediaPlayer); + verifyLocalStop(mockMediaPlayer); verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE); // This test is intended to strictly verify all interactions with MediaPlayer in a local @@ -383,7 +388,7 @@ public class RingtoneBuilderTest { // Prepare // Uses attributes with haptic channels enabled, but will abandon the MediaPlayer when it // knows there aren't any. - verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); + verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); verify(mockMediaPlayer).setVolume(0.0f); // Vibration-only: sound muted. verify(mockMediaPlayer).setLooping(false); verify(mockMediaPlayer).prepare(); @@ -438,7 +443,7 @@ public class RingtoneBuilderTest { // Prepare // Uses attributes with haptic channels enabled, but will use the effect when there aren't // any present. - verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); + verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); verify(mockMediaPlayer).setVolume(0.0f); // Vibration-only: sound muted. verify(mockMediaPlayer).setLooping(false); verify(mockMediaPlayer).prepare(); @@ -446,7 +451,7 @@ public class RingtoneBuilderTest { // Play ringtone.play(); // Vibrator.vibrate isn't called because the vibration comes from the sound. - verifyPlayerStarted(mockMediaPlayer); + verifyLocalPlay(mockMediaPlayer); // Verify dynamic controls (no-op without sound) ringtone.setVolume(0.8f); @@ -461,7 +466,7 @@ public class RingtoneBuilderTest { // Release ringtone.stop(); - verifyPlayerStopped(mockMediaPlayer); + verifyLocalStop(mockMediaPlayer); // This test is intended to strictly verify all interactions with MediaPlayer in a local // playback case. This shouldn't be necessary in other tests that have the same basic @@ -491,17 +496,17 @@ public class RingtoneBuilderTest { // Prepare // The attributes here have haptic channels enabled (unlike above) - verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); + verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); verify(mockMediaPlayer).prepare(); // Play ringtone.play(); when(mockMediaPlayer.isPlaying()).thenReturn(true); - verifyPlayerStarted(mockMediaPlayer); + verifyLocalPlay(mockMediaPlayer); // Release ringtone.stop(); - verifyPlayerStopped(mockMediaPlayer); + verifyLocalStop(mockMediaPlayer); verifyZeroInteractions(mMockRemotePlayer); // Nothing after the initial hasVibrator - it uses audio-coupled. @@ -531,7 +536,7 @@ public class RingtoneBuilderTest { // Prepare // The attributes here have haptic channels enabled (unlike above) - verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); + verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC); verify(mockMediaPlayer).prepare(); // Play @@ -554,7 +559,7 @@ public class RingtoneBuilderTest { @Test public void testRingtone_nullMediaOnBuilderUsesFallback() throws Exception { AssetFileDescriptor testResourceFd = - mContext.getResources().openRawResourceFd(R.raw.test_sound_file); + mContext.getResources().openRawResourceFd(R.raw.shortmp3); // Ensure it will flow as expected. assertThat(testResourceFd).isNotNull(); assertThat(testResourceFd.getDeclaredLength()).isAtLeast(0); @@ -570,18 +575,18 @@ public class RingtoneBuilderTest { // Delegates straight to fallback in local player. // Prepare - verifyPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES); + verifyLocalPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES); verify(mockMediaPlayer).setVolume(1.0f); verify(mockMediaPlayer).setLooping(false); verify(mockMediaPlayer).prepare(); // Play ringtone.play(); - verifyPlayerStarted(mockMediaPlayer); + verifyLocalPlay(mockMediaPlayer); // Release ringtone.stop(); - verifyPlayerStopped(mockMediaPlayer); + verifyLocalStop(mockMediaPlayer); verifyNoMoreInteractions(mockMediaPlayer); verifyNoMoreInteractions(mMockRemotePlayer); @@ -610,10 +615,24 @@ public class RingtoneBuilderTest { verifyNoMoreInteractions(mMockRemotePlayer); } + @Test + public void testRingtone_noMediaSetOnBuilderFallbackFailsAndNoRemote() throws Exception { + mContext.getOrCreateTestableResources() + .addOverride(com.android.internal.R.raw.fallbackring, null); + Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES) + .setUri(null) + .setLocalOnly() + .build(); + // Local player fallback fails as the resource isn't found (no media player creation is + // attempted), and since there is no local player, the ringtone ends up having nothing to + // do. + assertThat(ringtone).isNull(); + } + private Ringtone.Builder newBuilder(@Ringtone.RingtoneMedia int ringtoneMedia, AudioAttributes audioAttributes) { return new Ringtone.Builder(mContext, ringtoneMedia, audioAttributes) - .setInjectables(mMediaPlayerRule.getRingtoneTestInjectables()); + .setInjectables(mMediaPlayerRule.injectables); } private static AudioAttributes audioAttributes(int audioUsage) { @@ -628,4 +647,194 @@ public class RingtoneBuilderTest { doThrow(new FileNotFoundException("Fake file not found")) .when(mockMediaPlayer).setDataSource(any(Context.class), eq(uri)); } + + private void verifyLocalPlayerSetup(MediaPlayer mockPlayer, Uri expectedUri, + AudioAttributes expectedAudioAttributes) throws Exception { + verify(mockPlayer).setDataSource(mContext, expectedUri); + verify(mockPlayer).setAudioAttributes(expectedAudioAttributes); + verify(mockPlayer).setPreferredDevice(null); + verify(mockPlayer).prepare(); + } + + private void verifyLocalPlayerFallbackSetup(MediaPlayer mockPlayer, AssetFileDescriptor afd, + AudioAttributes expectedAudioAttributes) throws Exception { + // This is very specific but it's a simple way to test that the test resource matches. + if (afd.getDeclaredLength() < 0) { + verify(mockPlayer).setDataSource(afd.getFileDescriptor()); + } else { + verify(mockPlayer).setDataSource(afd.getFileDescriptor(), + afd.getStartOffset(), + afd.getDeclaredLength()); + } + verify(mockPlayer).setAudioAttributes(expectedAudioAttributes); + verify(mockPlayer).setPreferredDevice(null); + verify(mockPlayer).prepare(); + } + + private void verifyLocalPlay(MediaPlayer mockMediaPlayer) { + verify(mockMediaPlayer).setOnCompletionListener(any()); + verify(mockMediaPlayer).start(); + } + + private void verifyLocalStop(MediaPlayer mockMediaPlayer) { + verify(mockMediaPlayer).stop(); + verify(mockMediaPlayer).setOnCompletionListener(isNull()); + verify(mockMediaPlayer).reset(); + verify(mockMediaPlayer).release(); + } + + /** + * This rule ensures that all expected media player creations from the factory do actually + * occur. The reason for this level of control is that creating a media player is fairly + * expensive and blocking, so we do want unit tests of this class to "declare" interactions + * of all created media players. + * + * This needs to be a TestRule so that the teardown assertions can be skipped if the test has + * failed (and media player assertions may just be a distracting side effect). Otherwise, the + * teardown failures hide the real test ones. + */ + public static class RingtoneInjectablesTrackingTestRule implements TestRule { + public Ringtone.Injectables injectables = new TestInjectables(); + public boolean hapticGeneratorAvailable = true; + + // Queue of (local) media players, in order of expected creation. Enqueue using + // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone. + // This queue is asserted to be empty at the end of the test. + private Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>(); + + // Similar to media players, but for haptic generator, which also needs releasing. + private Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>(); + + // Media players with haptic channels. + private ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>(); + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + base.evaluate(); + // Only assert if the test didn't fail (base.evaluate() would throw). + assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed") + .that(mMockMediaPlayerQueue).isEmpty(); + // Only assert if the test didn't fail (base.evaluate() would throw). + assertWithMessage( + "Test setup an expectLocalHapticGenerator but it wasn't consumed") + .that(mMockHapticGeneratorMap).isEmpty(); + } + }; + } + + private TestMediaPlayer expectLocalMediaPlayer() { + TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class); + // Delegate to simulated methods. This means they can be verified but also reflect + // realistic transitions from the TestMediaPlayer. + doCallRealMethod().when(mockMediaPlayer).start(); + doCallRealMethod().when(mockMediaPlayer).stop(); + doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean()); + when(mockMediaPlayer.isLooping()).thenCallRealMethod(); + when(mockMediaPlayer.isLooping()).thenCallRealMethod(); + mMockMediaPlayerQueue.add(mockMediaPlayer); + return mockMediaPlayer; + } + + private HapticGenerator expectHapticGenerator(MediaPlayer mockMediaPlayer) { + HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class); + // A test should never want this. + assertWithMessage("Can't expect a second haptic generator created " + + "for one media player") + .that(mMockHapticGeneratorMap.put(mockMediaPlayer, mockHapticGenerator)) + .isNull(); + return mockHapticGenerator; + } + + private void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) { + if (hasHapticChannels) { + mHapticChannels.add(mp); + } else { + mHapticChannels.remove(mp); + } + } + + private class TestInjectables extends Ringtone.Injectables { + @Override + public MediaPlayer newMediaPlayer() { + assertWithMessage( + "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer") + .that(mMockMediaPlayerQueue) + .isNotEmpty(); + return mMockMediaPlayerQueue.remove(); + } + + @Override + public boolean isHapticGeneratorAvailable() { + return hapticGeneratorAvailable; + } + + @Override + public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) { + HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer); + assertWithMessage("Unexpected HapticGenerator creation. " + + "Bug or need expectHapticGenerator") + .that(mockHapticGenerator) + .isNotNull(); + return mockHapticGenerator; + } + + @Override + public boolean isHapticPlaybackSupported() { + return true; + } + + @Override + public boolean hasHapticChannels(MediaPlayer mp) { + return mHapticChannels.contains(mp); + } + } + } + + /** + * MediaPlayer relies on a native backend and so its necessary to intercept calls from + * fake usage hitting them. + * + * Mocks don't work directly on native calls, but if they're overridden then it does work. + * Some basic state faking is also done to make the mocks more realistic. + */ + private static class TestMediaPlayer extends MediaPlayer { + private boolean mIsPlaying = false; + private boolean mIsLooping = false; + + @Override + public void start() { + mIsPlaying = true; + } + + @Override + public void stop() { + mIsPlaying = false; + } + + @Override + public void setLooping(boolean value) { + mIsLooping = value; + } + + @Override + public boolean isLooping() { + return mIsLooping; + } + + @Override + public boolean isPlaying() { + return mIsPlaying; + } + + void simulatePlayingFinished() { + if (!mIsPlaying) { + throw new IllegalStateException( + "Attempted to pretend playing finished when not playing"); + } + mIsPlaying = false; + } + } } diff --git a/media/tests/ringtone/Android.bp b/media/tests/ringtone/Android.bp index 8d1e5e3a5bab..55b98c4704b1 100644 --- a/media/tests/ringtone/Android.bp +++ b/media/tests/ringtone/Android.bp @@ -9,24 +9,15 @@ android_test { srcs: ["src/**/*.java"], libs: [ - "android.test.base", - "android.test.mock", "android.test.runner", + "android.test.base", ], static_libs: [ - "androidx.test.ext.junit", - "androidx.test.ext.truth", "androidx.test.rules", - "frameworks-base-testutils", - "mockito-target-inline-minus-junit4", - "testables", "testng", - ], - - jni_libs: [ - "libdexmakerjvmtiagent", - "libstaticjvmtiagent", + "androidx.test.ext.truth", + "frameworks-base-testutils", ], test_suites: [ diff --git a/media/tests/ringtone/OWNERS b/media/tests/ringtone/OWNERS deleted file mode 100644 index 93b44f4788c5..000000000000 --- a/media/tests/ringtone/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -# Bug component: 345036 - -include /services/core/java/com/android/server/vibrator/OWNERS diff --git a/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java b/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java deleted file mode 100644 index e97e1173a1ea..000000000000 --- a/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 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.media.testing; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.verify; - -import android.content.Context; -import android.content.res.AssetFileDescriptor; -import android.media.AudioAttributes; -import android.media.MediaPlayer; -import android.net.Uri; - -/** - * Helper class with assertion methods on mock {@link MediaPlayer} instances. - */ -public final class MediaPlayerTestHelper { - - /** Verify this local media player mock instance was started. */ - public static void verifyPlayerStarted(MediaPlayer mockMediaPlayer) { - verify(mockMediaPlayer).setOnCompletionListener(any()); - verify(mockMediaPlayer).start(); - } - - /** Verify this local media player mock instance was stopped and released. */ - public static void verifyPlayerStopped(MediaPlayer mockMediaPlayer) { - verify(mockMediaPlayer).stop(); - verify(mockMediaPlayer).setOnCompletionListener(isNull()); - verify(mockMediaPlayer).reset(); - verify(mockMediaPlayer).release(); - } - - /** Verify this local media player mock instance was setup with given attributes. */ - public static void verifyPlayerSetup(Context context, MediaPlayer mockPlayer, - Uri expectedUri, AudioAttributes expectedAudioAttributes) throws Exception { - verify(mockPlayer).setDataSource(context, expectedUri); - verify(mockPlayer).setAudioAttributes(expectedAudioAttributes); - verify(mockPlayer).setPreferredDevice(null); - verify(mockPlayer).prepare(); - } - - /** Verify this local media player mock instance was setup with given fallback attributes. */ - public static void verifyPlayerFallbackSetup(MediaPlayer mockPlayer, - AssetFileDescriptor afd, AudioAttributes expectedAudioAttributes) throws Exception { - // This is very specific but it's a simple way to test that the test resource matches. - if (afd.getDeclaredLength() < 0) { - verify(mockPlayer).setDataSource(afd.getFileDescriptor()); - } else { - verify(mockPlayer).setDataSource(afd.getFileDescriptor(), - afd.getStartOffset(), - afd.getDeclaredLength()); - } - verify(mockPlayer).setAudioAttributes(expectedAudioAttributes); - verify(mockPlayer).setPreferredDevice(null); - verify(mockPlayer).prepare(); - } - - private MediaPlayerTestHelper() { - } -} diff --git a/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java b/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java deleted file mode 100644 index 25752ce83e5c..000000000000 --- a/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright 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.media.testing; - -import static com.google.common.truth.Truth.assertWithMessage; - -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Mockito.doCallRealMethod; -import static org.mockito.Mockito.when; - -import android.media.MediaPlayer; -import android.media.Ringtone; -import android.media.audiofx.HapticGenerator; -import android.util.ArrayMap; -import android.util.ArraySet; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; -import org.mockito.Mockito; - -import java.util.ArrayDeque; -import java.util.Map; -import java.util.Queue; - -/** - * This rule ensures that all expected media player creations from the factory do actually - * occur. The reason for this level of control is that creating a media player is fairly - * expensive and blocking, so we do want unit tests of this class to "declare" interactions - * of all created media players. - * <p> - * This needs to be a TestRule so that the teardown assertions can be skipped if the test has - * failed (and media player assertions may just be a distracting side effect). Otherwise, the - * teardown failures hide the real test ones. - */ -public class RingtoneInjectablesTrackingTestRule implements TestRule { - - private final Ringtone.Injectables mRingtoneTestInjectables = new TestInjectables(); - - // Queue of (local) media players, in order of expected creation. Enqueue using - // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone. - // This queue is asserted to be empty at the end of the test. - private final Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>(); - - // Similar to media players, but for haptic generator, which also needs releasing. - private final Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>(); - - // Media players with haptic channels. - private final ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>(); - - private boolean mHapticGeneratorAvailable = true; - - @Override - public Statement apply(Statement base, Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - base.evaluate(); - // Only assert if the test didn't fail (base.evaluate() would throw). - assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed") - .that(mMockMediaPlayerQueue).isEmpty(); - // Only assert if the test didn't fail (base.evaluate() would throw). - assertWithMessage( - "Test setup an expectLocalHapticGenerator but it wasn't consumed") - .that(mMockHapticGeneratorMap).isEmpty(); - } - }; - } - - /** The {@link Ringtone.Injectables} to be used for creating a testable {@link Ringtone}. */ - public Ringtone.Injectables getRingtoneTestInjectables() { - return mRingtoneTestInjectables; - } - - /** - * Create a test {@link MediaPlayer} that will be provided to the {@link Ringtone} instance - * created with {@link #getRingtoneTestInjectables()}. - * - * <p>If a media player is not created during the test execution after this method is called - * then the test will fail. It will also fail if the ringtone attempts to create one without - * this method being called first. - */ - public TestMediaPlayer expectLocalMediaPlayer() { - TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class); - // Delegate to simulated methods. This means they can be verified but also reflect - // realistic transitions from the TestMediaPlayer. - doCallRealMethod().when(mockMediaPlayer).start(); - doCallRealMethod().when(mockMediaPlayer).stop(); - doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean()); - when(mockMediaPlayer.isLooping()).thenCallRealMethod(); - mMockMediaPlayerQueue.add(mockMediaPlayer); - return mockMediaPlayer; - } - - /** - * Create a test {@link HapticGenerator} that will be provided to the {@link Ringtone} instance - * created with {@link #getRingtoneTestInjectables()}. - * - * <p>If a haptic generator is not created during the test execution after this method is called - * then the test will fail. It will also fail if the ringtone attempts to create one without - * this method being called first. - */ - public HapticGenerator expectHapticGenerator(MediaPlayer mediaPlayer) { - HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class); - // A test should never want this. - assertWithMessage("Can't expect a second haptic generator created " - + "for one media player") - .that(mMockHapticGeneratorMap.put(mediaPlayer, mockHapticGenerator)) - .isNull(); - return mockHapticGenerator; - } - - /** - * Configures the {@link MediaPlayer} to always return given flag when - * {@link Ringtone.Injectables#hasHapticChannels(MediaPlayer)} is called. - */ - public void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) { - if (hasHapticChannels) { - mHapticChannels.add(mp); - } else { - mHapticChannels.remove(mp); - } - } - - /** Test implementation of {@link Ringtone.Injectables} that uses the test rule setup. */ - private class TestInjectables extends Ringtone.Injectables { - @Override - public MediaPlayer newMediaPlayer() { - assertWithMessage( - "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer") - .that(mMockMediaPlayerQueue) - .isNotEmpty(); - return mMockMediaPlayerQueue.remove(); - } - - @Override - public boolean isHapticGeneratorAvailable() { - return mHapticGeneratorAvailable; - } - - @Override - public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) { - HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer); - assertWithMessage("Unexpected HapticGenerator creation. " - + "Bug or need expectHapticGenerator") - .that(mockHapticGenerator) - .isNotNull(); - return mockHapticGenerator; - } - - @Override - public boolean isHapticPlaybackSupported() { - return true; - } - - @Override - public boolean hasHapticChannels(MediaPlayer mp) { - return mHapticChannels.contains(mp); - } - } - - /** - * MediaPlayer relies on a native backend and so its necessary to intercept calls from - * fake usage hitting them. - * <p> - * Mocks don't work directly on native calls, but if they're overridden then it does work. - * Some basic state faking is also done to make the mocks more realistic. - */ - public static class TestMediaPlayer extends MediaPlayer { - private boolean mIsPlaying = false; - private boolean mIsLooping = false; - - @Override - public void start() { - mIsPlaying = true; - } - - @Override - public void stop() { - mIsPlaying = false; - } - - @Override - public void setLooping(boolean value) { - mIsLooping = value; - } - - @Override - public boolean isLooping() { - return mIsLooping; - } - - @Override - public boolean isPlaying() { - return mIsPlaying; - } - - /** - * Updates {@link #isPlaying()} result to false, if it's set to true. - * - * @throws IllegalStateException is {@link #isPlaying()} is already false - */ - public void simulatePlayingFinished() { - if (!mIsPlaying) { - throw new IllegalStateException( - "Attempted to pretend playing finished when not playing"); - } - mIsPlaying = false; - } - } -} diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java new file mode 100644 index 000000000000..a9fd380c2733 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java @@ -0,0 +1,46 @@ +/* + * 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.settingslib.testutils.shadow; + +import android.Manifest; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.hardware.display.ColorDisplayManager; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(ColorDisplayManager.class) +public class ShadowColorDisplayManager extends org.robolectric.shadows.ShadowColorDisplayManager { + + private boolean mIsReduceBrightColorsActivated; + + @Implementation + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setReduceBrightColorsActivated(boolean activated) { + mIsReduceBrightColorsActivated = activated; + return true; + } + + @Implementation + @SystemApi + public boolean isReduceBrightColorsActivated() { + return mIsReduceBrightColorsActivated; + } + +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index ce96bbfc7976..abc62c4682cc 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.round import com.android.compose.animation.scene.transformation.PropertyTransformation +import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.modifiers.thenIf import com.android.compose.ui.util.lerp @@ -196,29 +197,44 @@ private fun shouldDrawElement( state.fromScene == state.toScene || !layoutImpl.isTransitionReady(state) || state.fromScene !in element.sceneValues || - state.toScene !in element.sceneValues || - !isSharedElementEnabled(layoutImpl, state, element.key) + state.toScene !in element.sceneValues ) { return true } - val otherScene = - layoutImpl.scenes.getValue( - if (scene.key == state.fromScene) { - state.toScene - } else { - state.fromScene - } - ) - - // When the element is shared, draw the one in the highest scene unless it is a background, i.e. - // it is usually drawn below everything else. - val isHighestScene = scene.zIndex > otherScene.zIndex - return if (element.key.isBackground) { - !isHighestScene - } else { - isHighestScene + val sharedTransformation = sharedElementTransformation(layoutImpl, state, element.key) + if (sharedTransformation?.enabled == false) { + return true } + + return shouldDrawOrComposeSharedElement( + layoutImpl, + state, + scene.key, + element.key, + sharedTransformation, + ) +} + +internal fun shouldDrawOrComposeSharedElement( + layoutImpl: SceneTransitionLayoutImpl, + transition: TransitionState.Transition, + scene: SceneKey, + element: ElementKey, + sharedTransformation: SharedElementTransformation? +): Boolean { + val scenePicker = sharedTransformation?.scenePicker ?: DefaultSharedElementScenePicker + val fromScene = transition.fromScene + val toScene = transition.toScene + + return scenePicker.sceneDuringTransition( + element = element, + fromScene = fromScene, + toScene = toScene, + progress = transition::progress, + fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex, + toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex, + ) == scene } private fun isSharedElementEnabled( @@ -226,6 +242,14 @@ private fun isSharedElementEnabled( transition: TransitionState.Transition, element: ElementKey, ): Boolean { + return sharedElementTransformation(layoutImpl, transition, element)?.enabled ?: true +} + +internal fun sharedElementTransformation( + layoutImpl: SceneTransitionLayoutImpl, + transition: TransitionState.Transition, + element: ElementKey, +): SharedElementTransformation? { val spec = layoutImpl.transitions.transitionSpec(transition.fromScene, transition.toScene) val sharedInFromScene = spec.transformations(element, transition.fromScene).shared val sharedInToScene = spec.transformations(element, transition.toScene).shared @@ -238,7 +262,7 @@ private fun isSharedElementEnabled( ) } - return sharedInFromScene?.enabled ?: true + return sharedInFromScene } /** diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt index 6dbeb69ff450..fa385d014ccb 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt @@ -22,6 +22,8 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.runtime.Composable import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.snapshots.Snapshot import androidx.compose.ui.Modifier @@ -60,7 +62,16 @@ internal fun MovableElement( // which case we still need to draw it. val picture = remember { Picture() } - if (shouldComposeMovableElement(layoutImpl, scene.key, element)) { + // Whether we should compose the movable element here. The scene picker logic to know in + // which scene we should compose/draw a movable element might depend on the current + // transition progress, so we put this in a derivedStateOf to prevent many recompositions + // during the transition. + val shouldComposeMovableElement by + remember(layoutImpl, scene.key, element) { + derivedStateOf { shouldComposeMovableElement(layoutImpl, scene.key, element) } + } + + if (shouldComposeMovableElement) { Box( Modifier.drawWithCache { val width = size.width.toInt() @@ -172,14 +183,13 @@ private fun shouldComposeMovableElement( return scene == fromScene } - // If we are ready in both scenes, then compose in the scene that has the highest zIndex (unless - // it is a background) given that this is the one that is going to be drawn. - val isHighestScene = layoutImpl.scene(scene).zIndex > layoutImpl.scene(otherScene).zIndex - return if (element.key.isBackground) { - !isHighestScene - } else { - isHighestScene - } + return shouldDrawOrComposeSharedElement( + layoutImpl, + transitionState, + scene, + element.key, + sharedElementTransformation(layoutImpl, transitionState, element.key), + ) } private class MovableElementScopeImpl( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index 49669775fedd..7b7ddfa5ec4e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -120,8 +120,14 @@ interface TransitionBuilder : PropertyTransformationBuilder { * * @param enabled whether the matched element(s) should actually be shared in this transition. * Defaults to true. + * @param scenePicker the [SharedElementScenePicker] to use when deciding in which scene we + * should draw or compose this shared element. */ - fun sharedElement(matcher: ElementMatcher, enabled: Boolean = true) + fun sharedElement( + matcher: ElementMatcher, + enabled: Boolean = true, + scenePicker: SharedElementScenePicker = DefaultSharedElementScenePicker, + ) /** * Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and @@ -144,6 +150,44 @@ interface TransitionBuilder : PropertyTransformationBuilder { fun reversed(builder: TransitionBuilder.() -> Unit) } +interface SharedElementScenePicker { + /** + * Return the scene in which [element] should be drawn (when using `Modifier.element(key)`) or + * composed (when using `MovableElement(key)`) during the transition from [fromScene] to + * [toScene]. + */ + fun sceneDuringTransition( + element: ElementKey, + fromScene: SceneKey, + toScene: SceneKey, + progress: () -> Float, + fromSceneZIndex: Float, + toSceneZIndex: Float, + ): SceneKey +} + +object DefaultSharedElementScenePicker : SharedElementScenePicker { + override fun sceneDuringTransition( + element: ElementKey, + fromScene: SceneKey, + toScene: SceneKey, + progress: () -> Float, + fromSceneZIndex: Float, + toSceneZIndex: Float + ): SceneKey { + // By default shared elements are drawn in the highest scene possible, unless it is a + // background. + return if ( + (fromSceneZIndex > toSceneZIndex && !element.isBackground) || + (fromSceneZIndex < toSceneZIndex && element.isBackground) + ) { + fromScene + } else { + toScene + } + } +} + @TransitionDsl interface PropertyTransformationBuilder { /** diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index f1c27178391c..d2bfd91842ae 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -111,8 +111,12 @@ internal class TransitionBuilderImpl : TransitionBuilder { range = null } - override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) { - transformations.add(SharedElementTransformation(matcher, enabled)) + override fun sharedElement( + matcher: ElementMatcher, + enabled: Boolean, + scenePicker: SharedElementScenePicker, + ) { + transformations.add(SharedElementTransformation(matcher, enabled, scenePicker)) } override fun timestampRange( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt index 2ef8d56c6bc6..0db8469466ef 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt @@ -21,6 +21,7 @@ import com.android.compose.animation.scene.Element import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.Scene import com.android.compose.animation.scene.SceneTransitionLayoutImpl +import com.android.compose.animation.scene.SharedElementScenePicker import com.android.compose.animation.scene.TransitionState /** A transformation applied to one or more elements during a transition. */ @@ -48,6 +49,7 @@ sealed interface Transformation { internal class SharedElementTransformation( override val matcher: ElementMatcher, internal val enabled: Boolean, + internal val scenePicker: SharedElementScenePicker, ) : Transformation /** A transformation that is applied on the element during the whole transition. */ diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt index 4204cd5f0da0..83af630ab098 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt @@ -144,7 +144,36 @@ class MovableElementTest { rule.testTransition( fromSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(50.dp)) }, toSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(100.dp)) }, - transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) }, + transition = { + spec = tween(durationMillis = 16 * 4, easing = LinearEasing) + sharedElement( + TestElements.Foo, + scenePicker = + object : SharedElementScenePicker { + override fun sceneDuringTransition( + element: ElementKey, + fromScene: SceneKey, + toScene: SceneKey, + progress: () -> Float, + fromSceneZIndex: Float, + toSceneZIndex: Float + ): SceneKey { + assertThat(fromScene).isEqualTo(TestScenes.SceneA) + assertThat(toScene).isEqualTo(TestScenes.SceneB) + assertThat(fromSceneZIndex).isEqualTo(0) + assertThat(toSceneZIndex).isEqualTo(1) + + // Compose Foo in Scene A if progress < 0.65f, otherwise compose it + // in Scene B. + return if (progress() < 0.65f) { + TestScenes.SceneA + } else { + TestScenes.SceneB + } + } + } + ) + }, fromScene = TestScenes.SceneA, toScene = TestScenes.SceneB, ) { @@ -170,9 +199,12 @@ class MovableElementTest { at(32) { // During the transition, there is a single counter that is moved, with the current - // value. + // value. Given that progress = 0.5f, it is currently composed in SceneA. rule - .onNode(hasText("count: 3")) + .onNode( + hasText("count: 3") and + hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA)) + ) .assertIsDisplayed() .assertSizeIsEqualTo(75.dp, 75.dp) @@ -186,6 +218,26 @@ class MovableElementTest { .isEqualTo(1) } + at(48) { + // During the transition, there is a single counter that is moved, with the current + // value. Given that progress = 0.75f, it is currently composed in SceneB. + rule + .onNode( + hasText("count: 3") and + hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB)) + ) + .assertIsDisplayed() + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + after { // At the end of the transition, the counter still has the current value. rule diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 75e71e414262..9ac1e9f58dbc 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -730,6 +730,9 @@ <!-- Whether the communal service should be enabled --> <bool name="config_communalServiceEnabled">false</bool> + <!-- Component names of allowed communal widgets --> + <string-array name="config_communalWidgetAllowlist" translatable="false" /> + <!-- Component name of communal source service --> <string name="config_communalSourceComponent" translatable="false">@null</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 84e06e221b01..205c297cebc4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -3463,7 +3463,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Deprecated private boolean isUnlockWithFacePossible(int userId) { if (isFaceAuthInteractorEnabled()) { - return getFaceAuthInteractor().canFaceAuthRun(); + return getFaceAuthInteractor() != null + && getFaceAuthInteractor().isFaceAuthEnabledAndEnrolled(); } return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId); } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt new file mode 100644 index 000000000000..f9c4f29afee9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt @@ -0,0 +1,31 @@ +/* + * 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.systemui.communal.data.model + +import com.android.systemui.communal.shared.CommunalContentSize + +/** Metadata for the default widgets */ +data class CommunalWidgetMetadata( + /* Widget provider component name */ + val componentName: String, + + /* Defines the order in which the widget will be rendered in the grid. */ + val priority: Int, + + /* Supported sizes */ + val sizes: List<CommunalContentSize> +) diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt index e2a7d077a32c..f13b62fbfeb9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt @@ -27,13 +27,17 @@ import android.content.pm.PackageManager import android.os.UserManager import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.communal.data.model.CommunalWidgetMetadata import com.android.systemui.communal.shared.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.CommunalContentSize import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose @@ -45,15 +49,20 @@ import kotlinx.coroutines.flow.map interface CommunalWidgetRepository { /** A flow of provider info for the stopwatch widget, or null if widget is unavailable. */ val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> + + /** Widgets that are allowed to render in the glanceable hub */ + val communalWidgetAllowlist: List<CommunalWidgetMetadata> } @SysUISingleton class CommunalWidgetRepositoryImpl @Inject constructor( + @Application private val applicationContext: Context, private val appWidgetManager: AppWidgetManager, private val appWidgetHost: AppWidgetHost, broadcastDispatcher: BroadcastDispatcher, + communalRepository: CommunalRepository, private val packageManager: PackageManager, private val userManager: UserManager, private val userTracker: UserTracker, @@ -64,12 +73,18 @@ constructor( const val TAG = "CommunalWidgetRepository" const val WIDGET_LABEL = "Stopwatch" } + override val communalWidgetAllowlist: List<CommunalWidgetMetadata> private val logger = Logger(logBuffer, TAG) // Whether the [AppWidgetHost] is listening for updates. private var isHostListening = false + init { + communalWidgetAllowlist = + if (communalRepository.isCommunalEnabled) getWidgetAllowlist() else emptyList() + } + // Widgets that should be rendered in communal mode. private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf() @@ -129,6 +144,18 @@ constructor( return@map addWidget(providerInfo) } + private fun getWidgetAllowlist(): List<CommunalWidgetMetadata> { + val componentNames = + applicationContext.resources.getStringArray(R.array.config_communalWidgetAllowlist) + return componentNames.mapIndexed { index, name -> + CommunalWidgetMetadata( + componentName = name, + priority = componentNames.size - index, + sizes = listOf(CommunalContentSize.HALF) + ) + } + } + private fun startListening() { if (isHostListening) { return diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt new file mode 100644 index 000000000000..0bd7d86c972d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt @@ -0,0 +1,8 @@ +package com.android.systemui.communal.shared + +/** Supported sizes for communal content in the layout grid. */ +enum class CommunalContentSize { + FULL, + HALF, + THIRD, +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt index e7f835f7b858..c3aaef76cb2f 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt @@ -1,5 +1,6 @@ package com.android.systemui.deviceentry +import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepositoryModule import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule import dagger.Module @@ -7,6 +8,7 @@ import dagger.Module includes = [ DeviceEntryRepositoryModule::class, + DeviceEntryHapticsRepositoryModule::class, ], ) object DeviceEntryModule diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt new file mode 100644 index 000000000000..1458404446e6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt @@ -0,0 +1,72 @@ +/* + * 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.systemui.deviceentry.data.repository + +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** Interface for classes that can access device-entry haptics application state. */ +interface DeviceEntryHapticsRepository { + /** + * Whether a successful biometric haptic has been requested. Has not yet been handled if true. + */ + val successHapticRequest: Flow<Boolean> + + /** Whether an error biometric haptic has been requested. Has not yet been handled if true. */ + val errorHapticRequest: Flow<Boolean> + + fun requestSuccessHaptic() + fun handleSuccessHaptic() + fun requestErrorHaptic() + fun handleErrorHaptic() +} + +/** Encapsulates application state for device entry haptics. */ +@SysUISingleton +class DeviceEntryHapticsRepositoryImpl @Inject constructor() : DeviceEntryHapticsRepository { + private val _successHapticRequest = MutableStateFlow(false) + override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow() + + private val _errorHapticRequest = MutableStateFlow(false) + override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow() + + override fun requestSuccessHaptic() { + _successHapticRequest.value = true + } + + override fun handleSuccessHaptic() { + _successHapticRequest.value = false + } + + override fun requestErrorHaptic() { + _errorHapticRequest.value = true + } + + override fun handleErrorHaptic() { + _errorHapticRequest.value = false + } +} + +@Module +interface DeviceEntryHapticsRepositoryModule { + @Binds fun repository(impl: DeviceEntryHapticsRepositoryImpl): DeviceEntryHapticsRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt new file mode 100644 index 000000000000..53d6f737af8d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt @@ -0,0 +1,133 @@ +/* + * 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.systemui.deviceentry.domain.interactor + +import com.android.keyguard.logging.BiometricUnlockLogger +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepository +import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor +import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.util.kotlin.sample +import com.android.systemui.util.time.SystemClock +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.combineTransform +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart + +/** + * Business logic for device entry haptic events. Determines whether the haptic should play. In + * particular, there are extra guards for whether device entry error and successes hatpics should + * play when the physical fingerprint sensor is located on the power button. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class DeviceEntryHapticsInteractor +@Inject +constructor( + private val repository: DeviceEntryHapticsRepository, + fingerprintPropertyRepository: FingerprintPropertyRepository, + biometricSettingsRepository: BiometricSettingsRepository, + keyEventInteractor: KeyEventInteractor, + powerInteractor: PowerInteractor, + private val systemClock: SystemClock, + private val logger: BiometricUnlockLogger, +) { + private val powerButtonSideFpsEnrolled = + combineTransform( + fingerprintPropertyRepository.sensorType, + biometricSettingsRepository.isFingerprintEnrolledAndEnabled, + ) { sensorType, enrolledAndEnabled -> + if (sensorType == FingerprintSensorType.POWER_BUTTON) { + emit(enrolledAndEnabled) + } else { + emit(false) + } + } + .distinctUntilChanged() + private val powerButtonDown: Flow<Boolean> = keyEventInteractor.isPowerButtonDown + private val lastPowerButtonWakeup: Flow<Long> = + powerInteractor.detailedWakefulness + .filter { it.isAwakeFrom(WakeSleepReason.POWER_BUTTON) } + .map { systemClock.uptimeMillis() } + .onStart { + // If the power button hasn't been pressed, we still want this to evaluate to true: + // `uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs` + emit(recentPowerButtonPressThresholdMs * -1L - 1L) + } + + val playSuccessHaptic: Flow<Boolean> = + repository.successHapticRequest + .filter { it } + .sample( + combine( + powerButtonSideFpsEnrolled, + powerButtonDown, + lastPowerButtonWakeup, + ::Triple + ) + ) + .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> + val sideFpsAllowsHaptic = + !powerButtonDown && + systemClock.uptimeMillis() - lastPowerButtonWakeup > + recentPowerButtonPressThresholdMs + val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic + if (!allowHaptic) { + logger.d("Skip success haptic. Recent power button press or button is down.") + handleSuccessHaptic() // immediately handle, don't vibrate + } + allowHaptic + } + val playErrorHaptic: Flow<Boolean> = + repository.errorHapticRequest + .filter { it } + .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair)) + .map { (sideFpsEnrolled, powerButtonDown) -> + val allowHaptic = !sideFpsEnrolled || !powerButtonDown + if (!allowHaptic) { + logger.d("Skip error haptic. Power button is down.") + handleErrorHaptic() // immediately handle, don't vibrate + } + allowHaptic + } + + fun vibrateSuccess() { + repository.requestSuccessHaptic() + } + + fun vibrateError() { + repository.requestErrorHaptic() + } + + fun handleSuccessHaptic() { + repository.handleSuccessHaptic() + } + + fun handleErrorHaptic() { + repository.handleErrorHaptic() + } + + private val recentPowerButtonPressThresholdMs = 400L +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 4764f223d121..472cc24080d5 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -259,7 +259,7 @@ object Flags { // TODO(b/290652751): Tracking bug. @JvmField val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA = - unreleasedFlag("migrate_split_keyguard_bottom_area", teamfood = true) + releasedFlag("migrate_split_keyguard_bottom_area") // TODO(b/297037052): Tracking bug. @JvmField @@ -274,7 +274,7 @@ object Flags { /** Migrate the lock icon view to the new keyguard root view. */ // TODO(b/286552209): Tracking bug. - @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag("migrate_lock_icon", teamfood = true) + @JvmField val MIGRATE_LOCK_ICON = releasedFlag("migrate_lock_icon") // TODO(b/288276738): Tracking bug. @JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag("widget_on_keyguard") @@ -419,7 +419,7 @@ object Flags { releasedFlag("incompatible_charging_battery_icon") // TODO(b/293585143): Tracking Bug - val INSTANT_TETHER = unreleasedFlag("instant_tether") + val INSTANT_TETHER = unreleasedFlag("instant_tether", teamfood = true) // TODO(b/294588085): Tracking Bug val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks") @@ -503,10 +503,9 @@ object Flags { @Keep @JvmField val WM_ENABLE_PARTIAL_SCREEN_SHARING = - unreleasedFlag( - name = "record_task_content", + releasedFlag( + name = "enable_record_task_content", namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER, - teamfood = true ) // TODO(b/254512674): Tracking Bug diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt index 20d99d1e75fb..7b33e11a0c9c 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt @@ -32,6 +32,8 @@ data class SliderHapticFeedbackConfig( @FloatRange(from = 0.0, to = 1.0) val additionalVelocityMaxBump: Float = 0.15f, /** Additional time delta to wait between drag texture vibrations */ @FloatRange(from = 0.0) val deltaMillisForDragInterval: Float = 0f, + /** Progress threshold beyond which a new drag texture is delivered */ + @FloatRange(from = 0.0, to = 1.0) val deltaProgressForDragThreshold: Float = 0.015f, /** Number of low ticks in a drag texture composition. This is not expected to change */ val numberOfLowTicks: Int = 5, /** Maximum velocity allowed for vibration scaling. This is not expected to change. */ diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt index e6de156de0c4..f313fb3eef0f 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt @@ -46,6 +46,8 @@ class SliderHapticFeedbackProvider( private val positionAccelerateInterpolator = AccelerateInterpolator(config.progressInterpolatorFactor) private var dragTextureLastTime = clock.elapsedRealtime() + var dragTextureLastProgress = -1f + private set private val lowTickDurationMs = vibratorHelper.getPrimitiveDurations(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)[0] private var hasVibratedAtLowerBookend = false @@ -91,6 +93,9 @@ class SliderHapticFeedbackProvider( val elapsedSinceLastDrag = currentTime - dragTextureLastTime if (elapsedSinceLastDrag < thresholdUntilNextDragCallMillis) return + val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress) + if (deltaProgress < config.deltaProgressForDragThreshold) return + val velocityInterpolated = velocityAccelerateInterpolator.getInterpolation( min(absoluteVelocity / config.maxVelocityToScale, 1f) @@ -116,11 +121,14 @@ class SliderHapticFeedbackProvider( } vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING) dragTextureLastTime = currentTime + dragTextureLastProgress = normalizedSliderProgress } override fun onHandleAcquiredByTouch() {} - override fun onHandleReleasedFromTouch() {} + override fun onHandleReleasedFromTouch() { + dragTextureLastProgress = -1f + } override fun onLowerBookend() { if (!hasVibratedAtLowerBookend) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index a511713eddd3..119ade48d4f7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -28,6 +28,7 @@ import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardStatusViewComponent import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder @@ -44,6 +45,7 @@ import com.android.systemui.res.R import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.KeyguardIndicationController +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import javax.inject.Inject @@ -72,7 +74,9 @@ constructor( private val keyguardIndicationController: KeyguardIndicationController, private val lockIconViewController: LockIconViewController, private val shadeInteractor: ShadeInteractor, - private val interactionJankMonitor: InteractionJankMonitor + private val interactionJankMonitor: InteractionJankMonitor, + private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, + private val vibratorHelper: VibratorHelper, ) : CoreStartable { private var rootViewHandle: DisposableHandle? = null @@ -143,6 +147,8 @@ constructor( shadeInteractor, { keyguardStatusViewController!!.getClockController() }, interactionJankMonitor, + deviceEntryHapticsInteractor, + vibratorHelper, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt index e8740a4b24c5..654f2d106206 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt @@ -90,7 +90,7 @@ interface BiometricSettingsRepository { * If the current user can use face auth to enter the device. This is true when the user has * face auth enrolled, and is enabled in settings/device policy. */ - val isFaceAuthEnrolledAndEnabled: Flow<Boolean> + val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean> /** * If the current user can use face auth to enter the device right now. This is true when @@ -348,10 +348,11 @@ constructor( .and(isFingerprintBiometricAllowed) .stateIn(scope, SharingStarted.Eagerly, false) - override val isFaceAuthEnrolledAndEnabled: Flow<Boolean> = + override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean> = isFaceAuthenticationEnabled .and(isFaceEnrolled) .and(mobileConnectionsRepository.isAnySimSecure.isFalse()) + .stateIn(scope, SharingStarted.Eagerly, false) override val isFaceAuthCurrentlyAllowed: Flow<Boolean> = isFaceAuthEnrolledAndEnabled diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt index 89aca7631934..85b0f4fb864b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt @@ -41,6 +41,9 @@ interface KeyguardFaceAuthInteractor { /** Whether face auth is in lock out state. */ fun isLockedOut(): Boolean + /** Whether face auth is enrolled and enabled for the current user */ + fun isFaceAuthEnabledAndEnrolled(): Boolean + /** * Register listener for use from code that cannot use [authenticationStatus] or * [detectionStatus] diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt index f38bb2b519e7..fbadde63a6b9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt @@ -43,6 +43,7 @@ class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInt override fun isLockedOut(): Boolean = false override fun isEnabled() = false + override fun isFaceAuthEnabledAndEnrolled(): Boolean = false override fun registerListener(listener: FaceAuthenticationListener) {} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index 797dec2c9625..2641846251cc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -32,6 +32,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus @@ -81,6 +82,7 @@ constructor( private val facePropertyRepository: FacePropertyRepository, private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig, private val powerInteractor: PowerInteractor, + private val biometricSettingsRepository: BiometricSettingsRepository, ) : CoreStartable, KeyguardFaceAuthInteractor { private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf() @@ -149,7 +151,10 @@ constructor( .onEach { if (it) { faceAuthenticationLogger.faceLockedOut("Fingerprint locked out") - repository.setLockedOut(true) + // We don't care about this if face auth is not enabled. + if (isFaceAuthEnabledAndEnrolled()) { + repository.setLockedOut(true) + } } } .launchIn(applicationScope) @@ -263,6 +268,9 @@ constructor( } } + override fun isFaceAuthEnabledAndEnrolled(): Boolean = + biometricSettingsRepository.isFaceAuthEnrolledAndEnabled.value + private fun observeFaceAuthStateUpdates() { authenticationStatus .onEach { authStatusUpdate -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index c72e6ce0b7d6..4d5c503d1c4e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.DrawableRes +import android.view.HapticFeedbackConstants import android.view.View import android.view.View.OnLayoutChangeListener import android.view.ViewGroup @@ -29,6 +30,7 @@ import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.TransitionState @@ -38,6 +40,7 @@ import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ClockController import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.temporarydisplay.ViewPriority import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator @@ -45,6 +48,7 @@ import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo import javax.inject.Provider import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch /** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */ @@ -62,6 +66,8 @@ object KeyguardRootViewBinder { shadeInteractor: ShadeInteractor, clockControllerProvider: Provider<ClockController>?, interactionJankMonitor: InteractionJankMonitor?, + deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?, + vibratorHelper: VibratorHelper?, ): DisposableHandle { var onLayoutChangeListener: OnLayoutChange? = null val childViews = mutableMapOf<Int, View?>() @@ -177,6 +183,44 @@ object KeyguardRootViewBinder { } } } + + if (deviceEntryHapticsInteractor != null && vibratorHelper != null) { + launch { + deviceEntryHapticsInteractor.playSuccessHaptic + .filter { it } + .collect { + if ( + featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION) + ) { + vibratorHelper.performHapticFeedback( + view, + HapticFeedbackConstants.CONFIRM, + ) + } else { + vibratorHelper.vibrateAuthSuccess("device-entry::success") + } + deviceEntryHapticsInteractor.handleSuccessHaptic() + } + } + + launch { + deviceEntryHapticsInteractor.playErrorHaptic + .filter { it } + .collect { + if ( + featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION) + ) { + vibratorHelper.performHapticFeedback( + view, + HapticFeedbackConstants.REJECT, + ) + } else { + vibratorHelper.vibrateAuthSuccess("device-entry::error") + } + deviceEntryHapticsInteractor.handleErrorHaptic() + } + } + } } } viewModel.clockControllerProvider = clockControllerProvider @@ -189,7 +233,7 @@ object KeyguardRootViewBinder { view.setOnHierarchyChangeListener( object : OnHierarchyChangeListener { override fun onChildViewAdded(parent: View, child: View) { - childViews.put(child.id, view) + childViews.put(child.id, child) } override fun onChildViewRemoved(parent: View, child: View) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index c1c29c416902..692984a90a14 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -27,6 +27,7 @@ import android.hardware.display.DisplayManager import android.os.Bundle import android.os.Handler import android.os.IBinder +import android.view.ContextThemeWrapper import android.view.Display import android.view.Display.DEFAULT_DISPLAY import android.view.DisplayInfo @@ -45,6 +46,7 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder @@ -113,6 +115,7 @@ constructor( private val chipbarCoordinator: ChipbarCoordinator, private val keyguardStateController: KeyguardStateController, private val shadeInteractor: ShadeInteractor, + private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, ) { val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN) @@ -179,7 +182,11 @@ constructor( fun render() { mainHandler.post { - val previewContext = display?.let { context.createDisplayContext(it) } ?: context + val previewContext = + display?.let { + ContextThemeWrapper(context.createDisplayContext(it), context.getTheme()) + } + ?: context val rootView = FrameLayout(previewContext) @@ -334,6 +341,8 @@ constructor( shadeInteractor, null, // clock provider only needed for burn in null, // jank monitor not required for preview mode + null, // device entry haptics not required for preview mode + null, // device entry haptics not required for preview mode ) ) rootView.addView( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt index 9371d4e2d465..342a440d972b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt @@ -50,11 +50,8 @@ constructor( override fun addViews(constraintLayout: ConstraintLayout) { if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { - val view = - LayoutInflater.from(constraintLayout.context) - .inflate(R.layout.ambient_indication, constraintLayout, false) - - constraintLayout.addView(view) + LayoutInflater.from(constraintLayout.context) + .inflate(R.layout.ambient_indication, constraintLayout, true) } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt index 8634b0911391..a53f0f11c380 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt @@ -19,7 +19,11 @@ import android.media.projection.IMediaProjectionManager import android.os.Process import android.os.RemoteException import android.util.Log -import com.android.internal.util.FrameworkStatsLog +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject @@ -36,21 +40,23 @@ constructor(private val service: IMediaProjectionManager) { * * @param sessionCreationSource The entry point requesting permission to capture. */ - fun notifyPermissionProgress(state: Int, sessionCreationSource: Int) { - // TODO check that state & SessionCreationSource matches expected values - notifyToServer(state, sessionCreationSource) + fun notifyProjectionInitiated(sessionCreationSource: SessionCreationSource) { + notifyToServer( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED, + sessionCreationSource + ) } /** * Request to log that the permission request moved to the given state. * - * Should not be used for the initialization state, since that + * Should not be used for the initialization state, since that should use {@link + * MediaProjectionMetricsLogger#notifyProjectionInitiated(Int)} and pass the + * sessionCreationSource. */ fun notifyPermissionProgress(state: Int) { // TODO validate state is valid - notifyToServer( - state, - FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN) + notifyToServer(state, SessionCreationSource.UNKNOWN) } /** @@ -64,16 +70,21 @@ constructor(private val service: IMediaProjectionManager) { * Indicates the entry point for requesting the permission. Must be a valid state defined in * the SessionCreationSource enum. */ - private fun notifyToServer(state: Int, sessionCreationSource: Int) { + private fun notifyToServer(state: Int, sessionCreationSource: SessionCreationSource) { Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource") try { service.notifyPermissionRequestStateChange( - Process.myUid(), state, sessionCreationSource) + Process.myUid(), + state, + sessionCreationSource.toMetricsConstant() + ) } catch (e: RemoteException) { Log.e( TAG, - "Error notifying server of permission flow state $state from source $sessionCreationSource", - e) + "Error notifying server of permission flow state $state from source " + + "$sessionCreationSource", + e + ) } } @@ -81,3 +92,18 @@ constructor(private val service: IMediaProjectionManager) { const val TAG = "MediaProjectionMetricsLogger" } } + +enum class SessionCreationSource { + APP, + CAST, + SYSTEM_UI_SCREEN_RECORDER, + UNKNOWN; + + fun toMetricsConstant(): Int = + when (this) { + APP -> METRICS_CREATION_SOURCE_APP + CAST -> METRICS_CREATION_SOURCE_CAST + SYSTEM_UI_SCREEN_RECORDER -> METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER + UNKNOWN -> METRICS_CREATION_SOURCE_UNKNOWN + } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt index b5d3e913cadb..0bbcfd9de24c 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt @@ -87,14 +87,15 @@ class MediaProjectionAppSelectorActivity( override fun getLayoutResource() = R.layout.media_projection_app_selector - public override fun onCreate(bundle: Bundle?) { + public override fun onCreate(savedInstanceState: Bundle?) { lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) component = componentFactory.create( hostUserHandle = hostUserHandle, callingPackage = callingPackage, view = this, - resultHandler = this + resultHandler = this, + isFirstStart = savedInstanceState == null ) component.lifecycleObservers.forEach { lifecycle.addObserver(it) } @@ -113,7 +114,7 @@ class MediaProjectionAppSelectorActivity( reviewGrantedConsentRequired = intent.getBooleanExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, false) - super.onCreate(bundle) + super.onCreate(savedInstanceState) controller.init() // we override AppList's AccessibilityDelegate set in ResolverActivity.onCreate because in // our case this delegate must extend RecyclerViewAccessibilityDelegate, otherwise diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index 2217509167ef..8c6f307c84d6 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -146,6 +146,9 @@ interface MediaProjectionAppSelectorComponent { @BindsInstance @MediaProjectionAppSelector callingPackage: String?, @BindsInstance view: MediaProjectionAppSelectorView, @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler, + // Whether the app selector is starting for the first time. False when it is re-starting + // due to a config change. + @BindsInstance @MediaProjectionAppSelector isFirstStart: Boolean, ): MediaProjectionAppSelectorComponent } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt index fced117a8132..69132d3662d4 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt @@ -18,6 +18,8 @@ package com.android.systemui.mediaprojection.appselector import android.content.ComponentName import android.os.UserHandle +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader @@ -43,9 +45,17 @@ constructor( @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName, @MediaProjectionAppSelector private val callerPackageName: String?, private val thumbnailLoader: RecentTaskThumbnailLoader, + @MediaProjectionAppSelector private val isFirstStart: Boolean, + private val logger: MediaProjectionMetricsLogger, ) { fun init() { + // Only log during the first start of the app selector. + // Don't log when the app selector restarts due to a config change. + if (isFirstStart) { + logger.notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) + } + scope.launch { val recentTasks = recentTaskListProvider.loadRecentTasks() diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt index a9e6c53b3bcd..e9b458271ef7 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt @@ -22,6 +22,7 @@ import android.content.ComponentName data class RecentTask( val taskId: Int, + val displayId: Int, @UserIdInt val userId: Int, val topActivityComponent: ComponentName?, val baseIntentComponent: ComponentName?, diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt index aa4c4e55c718..730aa620690a 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt @@ -60,6 +60,7 @@ constructor( .map { RecentTask( it.taskId, + it.displayId, it.userId, it.topActivity, it.baseIntent?.component, diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt index fd1a683dc78f..ba837dba5354 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt @@ -130,10 +130,10 @@ constructor( view.width, view.height ) - activityOptions.setPendingIntentBackgroundActivityStartMode( + activityOptions.pendingIntentBackgroundActivityStartMode = MODE_BACKGROUND_ACTIVITY_START_ALLOWED - ) activityOptions.launchCookie = launchCookie + activityOptions.launchDisplayId = task.displayId activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle()) resultHandler.returnSelectedApp(launchCookie) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java index d08d0400f354..fa418fc8b98b 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -53,7 +53,9 @@ import android.view.Window; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; import com.android.systemui.mediaprojection.MediaProjectionServiceHelper; +import com.android.systemui.mediaprojection.SessionCreationSource; import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; @@ -74,6 +76,7 @@ public class MediaProjectionPermissionActivity extends Activity private final FeatureFlags mFeatureFlags; private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver; private final StatusBarManager mStatusBarManager; + private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; private String mPackageName; private int mUid; @@ -90,15 +93,17 @@ public class MediaProjectionPermissionActivity extends Activity @Inject public MediaProjectionPermissionActivity(FeatureFlags featureFlags, Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver, - StatusBarManager statusBarManager) { + StatusBarManager statusBarManager, + MediaProjectionMetricsLogger mediaProjectionMetricsLogger) { mFeatureFlags = featureFlags; mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver; mStatusBarManager = statusBarManager; + mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger; } @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); final Intent launchingIntent = getIntent(); mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra( @@ -133,6 +138,10 @@ public class MediaProjectionPermissionActivity extends Activity try { if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) { + if (savedInstanceState == null) { + mMediaProjectionMetricsLogger.notifyProjectionInitiated( + SessionCreationSource.APP); + } final IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName, mReviewGrantedConsentRequired); @@ -231,6 +240,13 @@ public class MediaProjectionPermissionActivity extends Activity mDialog = dialogBuilder.create(); } + if (savedInstanceState == null) { + mMediaProjectionMetricsLogger.notifyProjectionInitiated( + appName == null + ? SessionCreationSource.CAST + : SessionCreationSource.APP); + } + setUpDialog(mDialog); mDialog.show(); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index c4749e093854..c77f3f49a77c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -231,7 +231,6 @@ internal constructor( animation.removeEndListener(this) if (!canceled) { - // The delay between finishing this animation and starting the runnable val delay = max(0, runnableDelay - elapsedTimeSinceEntry) @@ -461,7 +460,6 @@ internal constructor( } private fun handleMoveEvent(event: MotionEvent) { - val x = event.x val y = event.y @@ -927,17 +925,7 @@ internal constructor( GestureState.ACTIVE -> { previousXTranslationOnActiveOffset = previousXTranslation updateRestingArrowDimens() - if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - vibratorHelper.performHapticFeedback( - mView, - HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE - ) - } else { - vibratorHelper.cancel() - mainHandler.postDelayed(10L) { - vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT) - } - } + performActivatedHapticFeedback() val popVelocity = if (previousState == GestureState.INACTIVE) { POP_ON_INACTIVE_TO_ACTIVE_VELOCITY @@ -958,25 +946,24 @@ internal constructor( mView.popOffEdge(POP_ON_INACTIVE_VELOCITY) - if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - vibratorHelper.performHapticFeedback( - mView, - HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE - ) - } else { - vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT) - } + performDeactivatedHapticFeedback() updateRestingArrowDimens() } GestureState.FLUNG -> { + // Typically a vibration is only played while transitioning to ACTIVE. However there + // are instances where a fling to trigger back occurs while not in that state. + // (e.g. A fling is detected before crossing the trigger threshold.) + if (previousState != GestureState.ACTIVE) { + performActivatedHapticFeedback() + } mainHandler.postDelayed(POP_ON_FLING_DELAY) { mView.popScale(POP_ON_FLING_VELOCITY) } - updateRestingArrowDimens() mainHandler.postDelayed( onEndSetCommittedStateListener.runnable, MIN_DURATION_FLING_ANIMATION ) + updateRestingArrowDimens() } GestureState.COMMITTED -> { // In most cases, animating between states is handled via `updateRestingArrowDimens` @@ -1011,6 +998,31 @@ internal constructor( } } + private fun performDeactivatedHapticFeedback() { + if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + vibratorHelper.performHapticFeedback( + mView, + HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE + ) + } else { + vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT) + } + } + + private fun performActivatedHapticFeedback() { + if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + vibratorHelper.performHapticFeedback( + mView, + HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE + ) + } else { + vibratorHelper.cancel() + mainHandler.postDelayed(10L) { + vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT) + } + } + } + private fun convertVelocityToAnimationFactor( valueOnFastVelocity: Float, valueOnSlowVelocity: Float, diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index 733383e344b8..58c4f0d029c7 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -96,6 +96,44 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +/** Variables and functions that is related to Emoji. */ +class EmojiHelper { + static final CharSequence EMOJI_CAKE = "\ud83c\udf82"; + + // This regex can be used to match Unicode emoji characters and character sequences. It's from + // the official Unicode site (https://unicode.org/reports/tr51/#EBNF_and_Regex) with minor + // changes to fit our needs. It should be updated once new emoji categories are added. + // + // Emoji categories that can be matched by this regex: + // - Country flags. "\p{RI}\p{RI}" matches country flags since they always consist of 2 Unicode + // scalars. + // - Single-Character Emoji. "\p{Emoji}" matches Single-Character Emojis. + // - Emoji with modifiers. E.g. Emojis with different skin tones. "\p{Emoji}\p{EMod}" matches + // them. + // - Emoji Presentation. Those are characters which can normally be drawn as either text or as + // Emoji. "\p{Emoji}\x{FE0F}" matches them. + // - Emoji Keycap. E.g. Emojis for number 0 to 9. "\p{Emoji}\x{FE0F}\x{20E3}" matches them. + // - Emoji tag sequence. "\p{Emoji}[\x{E0020}-\x{E007E}]+\x{E007F}" matches them. + // - Emoji Zero-Width Joiner (ZWJ) Sequence. A ZWJ emoji is actually multiple emojis joined by + // the jointer "0x200D". + // + // Note: since "\p{Emoji}" also matches some ASCII characters like digits 0-9, we use + // "\p{Emoji}&&\p{So}" to exclude them. This is the change we made from the official emoji + // regex. + private static final String UNICODE_EMOJI_REGEX = + "\\p{RI}\\p{RI}|" + + "(" + + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" + + "|[\\p{Emoji}&&\\p{So}]" + + ")" + + "(" + + "\\x{200D}" + + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" + + "?)*"; + + static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX); +} + /** Functions that help creating the People tile layouts. */ public class PeopleTileViewHelper { /** Turns on debugging information about People Space. */ @@ -125,8 +163,6 @@ public class PeopleTileViewHelper { private static final int MESSAGES_COUNT_OVERFLOW = 6; - private static final CharSequence EMOJI_CAKE = "\ud83c\udf82"; - private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+"); private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+"); private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+"); @@ -134,39 +170,6 @@ public class PeopleTileViewHelper { static final String BRIEF_PAUSE_ON_TALKBACK = "\n\n"; - // This regex can be used to match Unicode emoji characters and character sequences. It's from - // the official Unicode site (https://unicode.org/reports/tr51/#EBNF_and_Regex) with minor - // changes to fit our needs. It should be updated once new emoji categories are added. - // - // Emoji categories that can be matched by this regex: - // - Country flags. "\p{RI}\p{RI}" matches country flags since they always consist of 2 Unicode - // scalars. - // - Single-Character Emoji. "\p{Emoji}" matches Single-Character Emojis. - // - Emoji with modifiers. E.g. Emojis with different skin tones. "\p{Emoji}\p{EMod}" matches - // them. - // - Emoji Presentation. Those are characters which can normally be drawn as either text or as - // Emoji. "\p{Emoji}\x{FE0F}" matches them. - // - Emoji Keycap. E.g. Emojis for number 0 to 9. "\p{Emoji}\x{FE0F}\x{20E3}" matches them. - // - Emoji tag sequence. "\p{Emoji}[\x{E0020}-\x{E007E}]+\x{E007F}" matches them. - // - Emoji Zero-Width Joiner (ZWJ) Sequence. A ZWJ emoji is actually multiple emojis joined by - // the jointer "0x200D". - // - // Note: since "\p{Emoji}" also matches some ASCII characters like digits 0-9, we use - // "\p{Emoji}&&\p{So}" to exclude them. This is the change we made from the official emoji - // regex. - private static final String UNICODE_EMOJI_REGEX = - "\\p{RI}\\p{RI}|" - + "(" - + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" - + "|[\\p{Emoji}&&\\p{So}]" - + ")" - + "(" - + "\\x{200D}" - + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" - + "?)*"; - - private static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX); - public static final String EMPTY_STRING = ""; private int mMediumVerticalPadding; @@ -831,7 +834,7 @@ public class PeopleTileViewHelper { if (status.getActivity() == ACTIVITY_BIRTHDAY || status.getActivity() == ACTIVITY_UPCOMING_BIRTHDAY) { - setEmojiBackground(views, EMOJI_CAKE); + setEmojiBackground(views, EmojiHelper.EMOJI_CAKE); } Icon statusIcon = status.getIcon(); @@ -1072,7 +1075,7 @@ public class PeopleTileViewHelper { /** Returns emoji if {@code message} has two of the same emoji in sequence. */ @VisibleForTesting CharSequence getDoubleEmoji(CharSequence message) { - Matcher unicodeEmojiMatcher = EMOJI_PATTERN.matcher(message); + Matcher unicodeEmojiMatcher = EmojiHelper.EMOJI_PATTERN.matcher(message); // Stores the start and end indices of each matched emoji. List<Pair<Integer, Integer>> emojiIndices = new ArrayList<>(); // Stores each emoji text. diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index 7a0c087caacf..f469c6b78e87 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -38,6 +38,8 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; +import com.android.systemui.mediaprojection.SessionCreationSource; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; import com.android.systemui.plugins.ActivityStarter; @@ -45,13 +47,13 @@ import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.CallbackController; +import dagger.Lazy; + import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import javax.inject.Inject; -import dagger.Lazy; - /** * Helper class to initiate a screen recording */ @@ -71,6 +73,7 @@ public class RecordingController private final FeatureFlags mFlags; private final UserContextProvider mUserContextProvider; private final UserTracker mUserTracker; + private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; protected static final String INTENT_UPDATE_STATE = "com.android.systemui.screenrecord.UPDATE_STATE"; @@ -115,7 +118,8 @@ public class RecordingController FeatureFlags flags, UserContextProvider userContextProvider, Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver, - UserTracker userTracker) { + UserTracker userTracker, + MediaProjectionMetricsLogger mediaProjectionMetricsLogger) { mMainExecutor = mainExecutor; mContext = context; mFlags = flags; @@ -123,6 +127,7 @@ public class RecordingController mBroadcastDispatcher = broadcastDispatcher; mUserContextProvider = userContextProvider; mUserTracker = userTracker; + mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger; BroadcastOptions options = BroadcastOptions.makeBasic(); options.setInteractive(true); @@ -149,6 +154,9 @@ public class RecordingController return new ScreenCaptureDisabledDialog(mContext); } + mMediaProjectionMetricsLogger.notifyProjectionInitiated( + SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); + return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) ? new ScreenRecordPermissionDialog(context, getHostUserHandle(), this, activityStarter, mUserContextProvider, onStartRecordingClicked) diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index 6783afa3eb9d..1ecb127f0c92 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -21,6 +21,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY; import android.app.Activity; +import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.view.Gravity; @@ -35,8 +36,8 @@ import android.widget.FrameLayout; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -74,21 +75,26 @@ public class BrightnessDialog extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setWindowAttributes(); + setContentView(R.layout.brightness_mirror_container); + setBrightnessDialogViewAttributes(); + } + private void setWindowAttributes() { final Window window = getWindow(); - window.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); + window.setGravity(Gravity.TOP | Gravity.LEFT); window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); window.requestFeature(Window.FEATURE_NO_TITLE); // Calling this creates the decor View, so setLayout takes proper effect // (see Dialog#onWindowAttributesChanged) window.getDecorView(); - window.setLayout( - WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); + window.setLayout(WRAP_CONTENT, WRAP_CONTENT); getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false); + } - setContentView(R.layout.brightness_mirror_container); + void setBrightnessDialogViewAttributes() { FrameLayout frame = findViewById(R.id.brightness_mirror_container); // The brightness mirror container is INVISIBLE by default. frame.setVisibility(View.VISIBLE); @@ -97,6 +103,14 @@ public class BrightnessDialog extends Activity { getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); lp.leftMargin = horizontalMargin; lp.rightMargin = horizontalMargin; + + int verticalMargin = + getResources().getDimensionPixelSize( + R.dimen.notification_guts_option_vertical_padding); + + lp.topMargin = verticalMargin; + lp.bottomMargin = verticalMargin; + frame.setLayoutParams(lp); Rect bounds = new Rect(); frame.addOnLayoutChangeListener( @@ -113,6 +127,20 @@ public class BrightnessDialog extends Activity { frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT); mBrightnessController = mBrightnessControllerFactory.create(controller); + + Configuration configuration = getResources().getConfiguration(); + int orientation = configuration.orientation; + + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + lp.width = getWindowManager().getDefaultDisplay().getWidth() / 2 + - lp.leftMargin * 2; + } else if (orientation == Configuration.ORIENTATION_PORTRAIT) { + lp.width = getWindowManager().getDefaultDisplay().getWidth() + - lp.leftMargin * 2; + } + + frame.setLayoutParams(lp); + } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt deleted file mode 100644 index 17b4e3baef13..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Point -import android.graphics.Rect -import android.renderscript.Allocation -import android.renderscript.Element -import android.renderscript.RenderScript -import android.renderscript.ScriptIntrinsicBlur -import android.util.Log -import android.util.MathUtils -import com.android.internal.graphics.ColorUtils -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.notification.MediaNotificationProcessor -import javax.inject.Inject - -private const val TAG = "MediaArtworkProcessor" -private const val COLOR_ALPHA = (255 * 0.7f).toInt() -private const val BLUR_RADIUS = 25f -private const val DOWNSAMPLE = 6 - -@SysUISingleton -class MediaArtworkProcessor @Inject constructor() { - - private val mTmpSize = Point() - private var mArtworkCache: Bitmap? = null - - fun processArtwork(context: Context, artwork: Bitmap): Bitmap? { - if (mArtworkCache != null) { - return mArtworkCache - } - val renderScript = RenderScript.create(context) - val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)) - var input: Allocation? = null - var output: Allocation? = null - var inBitmap: Bitmap? = null - try { - @Suppress("DEPRECATION") - context.display?.getSize(mTmpSize) - val rect = Rect(0, 0, artwork.width, artwork.height) - MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE)) - inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(), - true /* filter */) - // Render script blurs only support ARGB_8888, we need a conversion if we got a - // different bitmap config. - if (inBitmap.config != Bitmap.Config.ARGB_8888) { - val oldIn = inBitmap - inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */) - oldIn.recycle() - } - val outBitmap = Bitmap.createBitmap(inBitmap?.width ?: 0, inBitmap?.height ?: 0, - Bitmap.Config.ARGB_8888) - - input = Allocation.createFromBitmap(renderScript, inBitmap, - Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE) - output = Allocation.createFromBitmap(renderScript, outBitmap) - - blur.setRadius(BLUR_RADIUS) - blur.setInput(input) - blur.forEach(output) - output.copyTo(outBitmap) - - val swatch = MediaNotificationProcessor.findBackgroundSwatch(artwork) - - val canvas = Canvas(outBitmap) - canvas.drawColor(ColorUtils.setAlphaComponent(swatch.rgb, COLOR_ALPHA)) - return outBitmap - } catch (ex: IllegalArgumentException) { - Log.e(TAG, "error while processing artwork", ex) - return null - } finally { - input?.destroy() - output?.destroy() - blur.destroy() - inBitmap?.recycle() - } - } - - fun clearCache() { - mArtworkCache?.recycle() - mArtworkCache = null - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 5bd40b8ed714..389486f0ada3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -15,43 +15,29 @@ */ package com.android.systemui.statusbar; -import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; -import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_MEDIA_FAKE_ARTWORK; -import static com.android.systemui.statusbar.phone.CentralSurfaces.ENABLE_LOCKSCREEN_WALLPAPER; -import static com.android.systemui.statusbar.phone.CentralSurfaces.SHOW_LOCKSCREEN_MEDIA_ARTWORK; - -import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.app.WallpaperManager; import android.content.Context; -import android.graphics.Bitmap; import android.graphics.Point; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.hardware.display.DisplayManager; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; -import android.os.AsyncTask; import android.os.Trace; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; -import android.util.ArraySet; import android.util.Log; import android.view.Display; import android.view.View; import android.widget.ImageView; -import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.controls.models.player.MediaData; import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData; @@ -65,18 +51,11 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Di import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.phone.BiometricUnlockController; -import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.LockscreenWallpaper; import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.phone.ScrimState; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.Utils; -import com.android.systemui.util.concurrency.DelayableExecutor; - -import dagger.Lazy; import java.io.PrintWriter; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -85,7 +64,6 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.stream.Collectors; /** @@ -99,7 +77,6 @@ public class NotificationMediaManager implements Dumpable { private final StatusBarStateController mStatusBarStateController; private final SysuiColorExtractor mColorExtractor; private final KeyguardStateController mKeyguardStateController; - private final KeyguardBypassController mKeyguardBypassController; private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>(); private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>(); static { @@ -117,9 +94,6 @@ public class NotificationMediaManager implements Dumpable { private final NotifCollection mNotifCollection; @Nullable - private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController; - - @Nullable private BiometricUnlockController mBiometricUnlockController; @Nullable private ScrimController mScrimController; @@ -128,12 +102,8 @@ public class NotificationMediaManager implements Dumpable { @VisibleForTesting boolean mIsLockscreenLiveWallpaperEnabled; - private final DelayableExecutor mMainExecutor; - private final Context mContext; private final ArrayList<MediaListener> mMediaListeners; - private final MediaArtworkProcessor mMediaArtworkProcessor; - private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>(); protected NotificationPresenter mPresenter; private MediaController mMediaController; @@ -150,8 +120,6 @@ public class NotificationMediaManager implements Dumpable { private List<String> mSmallerInternalDisplayUids; private Display mCurrentDisplay; - private LockscreenWallpaper.WallpaperDrawable mWallapperDrawable; - private final MediaController.Callback mMediaListener = new MediaController.Callback() { @Override public void onPlaybackStateChanged(PlaybackState state) { @@ -173,7 +141,6 @@ public class NotificationMediaManager implements Dumpable { if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); } - mMediaArtworkProcessor.clearCache(); mMediaMetadata = metadata; dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */); } @@ -184,13 +151,9 @@ public class NotificationMediaManager implements Dumpable { */ public NotificationMediaManager( Context context, - Lazy<NotificationShadeWindowController> notificationShadeWindowController, NotificationVisibilityProvider visibilityProvider, - MediaArtworkProcessor mediaArtworkProcessor, - KeyguardBypassController keyguardBypassController, NotifPipeline notifPipeline, NotifCollection notifCollection, - @Main DelayableExecutor mainExecutor, MediaDataManager mediaDataManager, StatusBarStateController statusBarStateController, SysuiColorExtractor colorExtractor, @@ -199,12 +162,8 @@ public class NotificationMediaManager implements Dumpable { WallpaperManager wallpaperManager, DisplayManager displayManager) { mContext = context; - mMediaArtworkProcessor = mediaArtworkProcessor; - mKeyguardBypassController = keyguardBypassController; mMediaListeners = new ArrayList<>(); - mNotificationShadeWindowController = notificationShadeWindowController; mVisibilityProvider = visibilityProvider; - mMainExecutor = mainExecutor; mMediaDataManager = mediaDataManager; mNotifPipeline = notifPipeline; mNotifCollection = notifCollection; @@ -476,7 +435,6 @@ public class NotificationMediaManager implements Dumpable { } private void clearCurrentMediaNotificationSession() { - mMediaArtworkProcessor.clearCache(); mMediaMetadata = null; if (mMediaController != null) { if (DEBUG_MEDIA) { @@ -494,9 +452,6 @@ public class NotificationMediaManager implements Dumpable { public void onDisplayUpdated(Display display) { Trace.beginSection("NotificationMediaManager#onDisplayUpdated"); mCurrentDisplay = display; - if (mWallapperDrawable != null) { - mWallapperDrawable.onDisplayUpdated(isOnSmallerInternalDisplays()); - } Trace.endSection(); } @@ -531,18 +486,13 @@ public class NotificationMediaManager implements Dumpable { } /** - * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper. + * Update media state of lockscreen media views and controllers . */ - public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { + public void updateMediaMetaData(boolean metaDataChanged) { if (mIsLockscreenLiveWallpaperEnabled) return; Trace.beginSection("CentralSurfaces#updateMediaMetaData"); - if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) { - Trace.endSection(); - return; - } - if (getBackDropView() == null) { Trace.endSection(); return; // called too early @@ -566,168 +516,12 @@ public class NotificationMediaManager implements Dumpable { + " state=" + mStatusBarStateController.getState()); } - Bitmap artworkBitmap = null; - if (mediaMetadata != null && !mKeyguardBypassController.getBypassEnabled()) { - artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART); - if (artworkBitmap == null) { - artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); - } - } - - // Process artwork on a background thread and send the resulting bitmap to - // finishUpdateMediaMetaData. - if (metaDataChanged) { - for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) { - task.cancel(true); - } - mProcessArtworkTasks.clear(); - } - if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) { - mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged, - allowEnterAnimation).execute(artworkBitmap)); - } else { - finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null); - } - - Trace.endSection(); - } - - private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation, - @Nullable Bitmap bmp) { - Drawable artworkDrawable = null; - if (bmp != null) { - artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp); - } - boolean hasMediaArtwork = artworkDrawable != null; - boolean allowWhenShade = false; - if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) { - Bitmap lockWallpaper = - mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null; - if (lockWallpaper != null) { - artworkDrawable = new LockscreenWallpaper.WallpaperDrawable( - mBackdropBack.getResources(), lockWallpaper, isOnSmallerInternalDisplays()); - // We're in the SHADE mode on the SIM screen - yet we still need to show - // the lockscreen wallpaper in that mode. - allowWhenShade = mStatusBarStateController.getState() == KEYGUARD; - } - } - - NotificationShadeWindowController windowController = - mNotificationShadeWindowController.get(); - boolean hideBecauseOccluded = mKeyguardStateController.isOccluded(); - - final boolean hasArtwork = artworkDrawable != null; - mColorExtractor.setHasMediaArtwork(hasMediaArtwork); + mColorExtractor.setHasMediaArtwork(false); if (mScrimController != null) { - mScrimController.setHasBackdrop(hasArtwork); + mScrimController.setHasBackdrop(false); } - if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) - && (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade) - && mBiometricUnlockController != null && mBiometricUnlockController.getMode() - != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING - && !hideBecauseOccluded) { - // time to show some art! - if (mBackdrop.getVisibility() != View.VISIBLE) { - mBackdrop.setVisibility(View.VISIBLE); - if (allowEnterAnimation) { - mBackdrop.setAlpha(0); - mBackdrop.animate().alpha(1f); - } else { - mBackdrop.animate().cancel(); - mBackdrop.setAlpha(1f); - } - if (windowController != null) { - windowController.setBackdropShowing(true); - } - metaDataChanged = true; - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork"); - } - } - if (metaDataChanged) { - if (mBackdropBack.getDrawable() != null) { - Drawable drawable = - mBackdropBack.getDrawable().getConstantState() - .newDrawable(mBackdropFront.getResources()).mutate(); - mBackdropFront.setImageDrawable(drawable); - mBackdropFront.setAlpha(1f); - mBackdropFront.setVisibility(View.VISIBLE); - } else { - mBackdropFront.setVisibility(View.INVISIBLE); - } - - if (DEBUG_MEDIA_FAKE_ARTWORK) { - final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF); - Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c)); - mBackdropBack.setBackgroundColor(0xFFFFFFFF); - mBackdropBack.setImageDrawable(new ColorDrawable(c)); - } else { - if (artworkDrawable instanceof LockscreenWallpaper.WallpaperDrawable) { - mWallapperDrawable = - (LockscreenWallpaper.WallpaperDrawable) artworkDrawable; - } - mBackdropBack.setImageDrawable(artworkDrawable); - } - - if (mBackdropFront.getVisibility() == View.VISIBLE) { - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from " - + mBackdropFront.getDrawable() - + " to " - + mBackdropBack.getDrawable()); - } - mBackdropFront.animate() - .setDuration(250) - .alpha(0f).withEndAction(mHideBackdropFront); - } - } - } else { - // need to hide the album art, either because we are unlocked, on AOD - // or because the metadata isn't there to support it - if (mBackdrop.getVisibility() != View.GONE) { - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork"); - } - boolean cannotAnimateDoze = mStatusBarStateController.isDozing() - && !ScrimState.AOD.getAnimateChange(); - if (((mBiometricUnlockController != null && mBiometricUnlockController.getMode() - == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING - || cannotAnimateDoze)) - || hideBecauseOccluded) { - // We are unlocking directly - no animation! - mBackdrop.setVisibility(View.GONE); - mBackdropBack.setImageDrawable(null); - if (windowController != null) { - windowController.setBackdropShowing(false); - } - } else { - if (windowController != null) { - windowController.setBackdropShowing(false); - } - mBackdrop.animate() - .alpha(0) - .setInterpolator(Interpolators.ACCELERATE_DECELERATE) - .setDuration(300) - .setStartDelay(0) - .withEndAction(() -> { - mBackdrop.setVisibility(View.GONE); - mBackdropFront.animate().cancel(); - mBackdropBack.setImageDrawable(null); - mMainExecutor.execute(mHideBackdropFront); - }); - if (mKeyguardStateController.isKeyguardFadingAway()) { - mBackdrop.animate() - .setDuration( - mKeyguardStateController.getShortenedFadingAwayDuration()) - .setStartDelay( - mKeyguardStateController.getKeyguardFadingAwayDelay()) - .setInterpolator(Interpolators.LINEAR) - .start(); - } - } - } - } + Trace.endSection(); } public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack, @@ -758,15 +552,6 @@ public class NotificationMediaManager implements Dumpable { } }; - private Bitmap processArtwork(Bitmap artwork) { - return mMediaArtworkProcessor.processArtwork(mContext, artwork); - } - - @MainThread - private void removeTask(AsyncTask<?, ?, ?> task) { - mProcessArtworkTasks.remove(task); - } - // TODO(b/273443374): remove public boolean isLockscreenWallpaperOnNotificationShade() { return mBackdrop != null && mLockscreenWallpaper != null @@ -780,52 +565,6 @@ public class NotificationMediaManager implements Dumpable { return mBackdrop; } - /** - * {@link AsyncTask} to prepare album art for use as backdrop on lock screen. - */ - private static final class ProcessArtworkTask extends AsyncTask<Bitmap, Void, Bitmap> { - - private final WeakReference<NotificationMediaManager> mManagerRef; - private final boolean mMetaDataChanged; - private final boolean mAllowEnterAnimation; - - ProcessArtworkTask(NotificationMediaManager manager, boolean changed, - boolean allowAnimation) { - mManagerRef = new WeakReference<>(manager); - mMetaDataChanged = changed; - mAllowEnterAnimation = allowAnimation; - } - - @Override - protected Bitmap doInBackground(Bitmap... bitmaps) { - NotificationMediaManager manager = mManagerRef.get(); - if (manager == null || bitmaps.length == 0 || isCancelled()) { - return null; - } - return manager.processArtwork(bitmaps[0]); - } - - @Override - protected void onPostExecute(@Nullable Bitmap result) { - NotificationMediaManager manager = mManagerRef.get(); - if (manager != null && !isCancelled()) { - manager.removeTask(this); - manager.finishUpdateMediaMetaData(mMetaDataChanged, mAllowEnterAnimation, result); - } - } - - @Override - protected void onCancelled(Bitmap result) { - if (result != null) { - result.recycle(); - } - NotificationMediaManager manager = mManagerRef.get(); - if (manager != null) { - manager.removeTask(this); - } - } - } - public interface MediaListener { /** * Called whenever there's new metadata or playback state. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java index 63282d2904e8..17da015789ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java @@ -71,7 +71,8 @@ public class RemoteInputController { * @param entry the entry for which a remote input is now active. * @param token a token identifying the view that is managing the remote input */ - public void addRemoteInput(NotificationEntry entry, Object token) { + public void addRemoteInput(NotificationEntry entry, Object token, + @CompileTimeConstant String reason) { Objects.requireNonNull(entry); Objects.requireNonNull(token); boolean isActive = isRemoteInputActive(entry); @@ -79,7 +80,9 @@ public class RemoteInputController { entry /* contains */, null /* remove */, token /* removeToken */); mLogger.logAddRemoteInput(entry.getKey()/* entryKey */, isActive /* isRemoteInputAlreadyActive */, - found /* isRemoteInputFound */); + found /* isRemoteInputFound */, + reason /* reason */, + entry.getNotificationStyle()/* notificationStyle */); if (!found) { mOpen.add(new Pair<>(new WeakReference<>(entry), token)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 7f5829d81c6f..125c8efe1884 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -31,7 +31,6 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpHandler; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; @@ -45,12 +44,10 @@ import com.android.systemui.shade.ShadeSurface; import com.android.systemui.shade.carrier.ShadeCarrierGroupController; import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.MediaArtworkProcessor; import com.android.systemui.statusbar.NotificationClickNotifier; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -61,7 +58,6 @@ import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.phone.CentralSurfacesImpl; -import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl; import com.android.systemui.statusbar.phone.StatusBarIconController; @@ -71,7 +67,6 @@ import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.RemoteInputUriController; -import com.android.systemui.util.concurrency.DelayableExecutor; import dagger.Binds; import dagger.Lazy; @@ -122,13 +117,9 @@ public interface CentralSurfacesDependenciesModule { @Provides static NotificationMediaManager provideNotificationMediaManager( Context context, - Lazy<NotificationShadeWindowController> notificationShadeWindowController, NotificationVisibilityProvider visibilityProvider, - MediaArtworkProcessor mediaArtworkProcessor, - KeyguardBypassController keyguardBypassController, NotifPipeline notifPipeline, NotifCollection notifCollection, - @Main DelayableExecutor mainExecutor, MediaDataManager mediaDataManager, StatusBarStateController statusBarStateController, SysuiColorExtractor colorExtractor, @@ -138,13 +129,9 @@ public interface CentralSurfacesDependenciesModule { DisplayManager displayManager) { return new NotificationMediaManager( context, - notificationShadeWindowController, visibilityProvider, - mediaArtworkProcessor, - keyguardBypassController, notifPipeline, notifCollection, - mainExecutor, mediaDataManager, statusBarStateController, colorExtractor, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java deleted file mode 100644 index 732c115571f8..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.notification; - -import android.graphics.Bitmap; -import android.graphics.Color; - -import androidx.palette.graphics.Palette; - -import java.util.List; - -/** - * A gutted class that now contains only a color extraction utility used by the - * MediaArtworkProcessor, which has otherwise supplanted this. - * - * TODO(b/182926117): move this into MediaArtworkProcessor.kt - */ -public class MediaNotificationProcessor { - - /** - * The population fraction to select a white or black color as the background over a color. - */ - private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f; - private static final float BLACK_MAX_LIGHTNESS = 0.08f; - private static final float WHITE_MIN_LIGHTNESS = 0.90f; - private static final int RESIZE_BITMAP_AREA = 150 * 150; - - private MediaNotificationProcessor() { - } - - /** - * Finds an appropriate background swatch from media artwork. - * - * @param artwork Media artwork - * @return Swatch that should be used as the background of the media notification. - */ - public static Palette.Swatch findBackgroundSwatch(Bitmap artwork) { - return findBackgroundSwatch(generateArtworkPaletteBuilder(artwork).generate()); - } - - /** - * Finds an appropriate background swatch from the palette of media artwork. - * - * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder} - * @return Swatch that should be used as the background of the media notification. - */ - public static Palette.Swatch findBackgroundSwatch(Palette palette) { - // by default we use the dominant palette - Palette.Swatch dominantSwatch = palette.getDominantSwatch(); - if (dominantSwatch == null) { - return new Palette.Swatch(Color.WHITE, 100); - } - - if (!isWhiteOrBlack(dominantSwatch.getHsl())) { - return dominantSwatch; - } - // Oh well, we selected black or white. Lets look at the second color! - List<Palette.Swatch> swatches = palette.getSwatches(); - float highestNonWhitePopulation = -1; - Palette.Swatch second = null; - for (Palette.Swatch swatch : swatches) { - if (swatch != dominantSwatch - && swatch.getPopulation() > highestNonWhitePopulation - && !isWhiteOrBlack(swatch.getHsl())) { - second = swatch; - highestNonWhitePopulation = swatch.getPopulation(); - } - } - if (second == null) { - return dominantSwatch; - } - if (dominantSwatch.getPopulation() / highestNonWhitePopulation - > POPULATION_FRACTION_FOR_WHITE_OR_BLACK) { - // The dominant swatch is very dominant, lets take it! - // We're not filtering on white or black - return dominantSwatch; - } else { - return second; - } - } - - /** - * Generate a palette builder for media artwork. - * - * For producing a smooth background transition, the palette is extracted from only the left - * side of the artwork. - * - * @param artwork Media artwork - * @return Builder that generates the {@link Palette} for the media artwork. - */ - public static Palette.Builder generateArtworkPaletteBuilder(Bitmap artwork) { - // for the background we only take the left side of the image to ensure - // a smooth transition - return Palette.from(artwork) - .setRegion(0, 0, artwork.getWidth() / 2, artwork.getHeight()) - .clearFilters() // we want all colors, red / white / black ones too! - .resizeBitmapArea(RESIZE_BITMAP_AREA); - } - - private static boolean isWhiteOrBlack(float[] hsl) { - return isBlack(hsl) || isWhite(hsl); - } - - /** - * @return true if the color represents a color which is close to black. - */ - private static boolean isBlack(float[] hslColor) { - return hslColor[2] <= BLACK_MAX_LIGHTNESS; - } - - /** - * @return true if the color represents a color which is close to white. - */ - private static boolean isWhite(float[] hslColor) { - return hslColor[2] >= WHITE_MIN_LIGHTNESS; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt index 97770316442c..ff89c62ab496 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt @@ -32,17 +32,24 @@ constructor(@NotificationRemoteInputLog private val logBuffer: LogBuffer) { fun logAddRemoteInput( entryKey: String, isRemoteInputAlreadyActive: Boolean, - isRemoteInputFound: Boolean + isRemoteInputFound: Boolean, + reason: String, + notificationStyle: String ) = logBuffer.log( TAG, DEBUG, { str1 = entryKey + str2 = reason + str3 = notificationStyle bool1 = isRemoteInputAlreadyActive bool2 = isRemoteInputFound }, - { "addRemoteInput entry: $str1, isAlreadyActive: $bool1, isFound:$bool2" } + { + "addRemoteInput reason:$str2 entry: $str1, style:$str3" + + ", isAlreadyActive: $bool1, isFound:$bool2" + } ) /** logs removeRemoteInput invocation of [RemoteInputController] */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 2809cad34143..8129b83a22d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -17,8 +17,6 @@ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.SESSION_KEYGUARD; -import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; -import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME; import android.annotation.IntDef; import android.content.res.Resources; @@ -30,7 +28,6 @@ import android.metrics.LogMaker; import android.os.Handler; import android.os.PowerManager; import android.os.Trace; -import android.view.HapticFeedbackConstants; import androidx.annotation.Nullable; @@ -47,16 +44,17 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; import com.android.keyguard.logging.BiometricUnlockLogger; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.biometrics.AuthController; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.res.R; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; @@ -73,12 +71,14 @@ import java.util.Set; import javax.inject.Inject; +import kotlinx.coroutines.ExperimentalCoroutinesApi; + /** * Controller which coordinates all the biometric unlocking actions with the UI. */ +@ExperimentalCoroutinesApi @SysUISingleton public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable { - private static final long RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS = 400L; private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000; private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock"; private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl(); @@ -175,6 +175,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private final BiometricUnlockLogger mLogger; private final SystemClock mSystemClock; private final boolean mOrderUnlockAndWake; + private final DeviceEntryHapticsInteractor mHapticsInteractor; private long mLastFpFailureUptimeMillis; private int mNumConsecutiveFpFailures; @@ -284,7 +285,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp ScreenOffAnimationController screenOffAnimationController, VibratorHelper vibrator, SystemClock systemClock, - FeatureFlags featureFlags + FeatureFlags featureFlags, + DeviceEntryHapticsInteractor hapticsInteractor ) { mPowerManager = powerManager; mUpdateMonitor = keyguardUpdateMonitor; @@ -314,6 +316,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mFeatureFlags = featureFlags; mOrderUnlockAndWake = resources.getBoolean( com.android.internal.R.bool.config_orderUnlockAndWake); + mHapticsInteractor = hapticsInteractor; dumpManager.registerDumpable(this); } @@ -434,7 +437,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp if (mode == MODE_WAKE_AND_UNLOCK || mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) { - vibrateSuccess(biometricSourceType); + mHapticsInteractor.vibrateSuccess(); onBiometricUnlockedWithKeyguardDismissal(biometricSourceType); } startWakeAndUnlock(mode); @@ -498,8 +501,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp case MODE_WAKE_AND_UNLOCK: if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) { Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING"); - mMediaManager.updateMediaMetaData(false /* metaDataChanged */, - true /* allowEnterAnimation */); + mMediaManager.updateMediaMetaData(false /* metaDataChanged */); } else if (mMode == MODE_WAKE_AND_UNLOCK){ Trace.beginSection("MODE_WAKE_AND_UNLOCK"); } else { @@ -723,7 +725,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp && !mUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( KeyguardUpdateMonitor.getCurrentUser())) || (biometricSourceType == BiometricSourceType.FINGERPRINT)) { - vibrateError(biometricSourceType); + mHapticsInteractor.vibrateError(); } cleanup(); @@ -750,45 +752,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp cleanup(); } - // these haptics are for device-entry only - private void vibrateSuccess(BiometricSourceType type) { - if (mAuthController.isSfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()) - && lastWakeupFromPowerButtonWithinHapticThreshold()) { - mLogger.d("Skip auth success haptic. Power button was recently pressed."); - return; - } - if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - mVibratorHelper.performHapticFeedback( - mKeyguardViewController.getViewRootImpl().getView(), - HapticFeedbackConstants.CONFIRM - ); - } else { - mVibratorHelper.vibrateAuthSuccess( - getClass().getSimpleName() + ", type =" + type + "device-entry::success"); - } - } - - private boolean lastWakeupFromPowerButtonWithinHapticThreshold() { - final boolean lastWakeupFromPowerButton = mWakefulnessLifecycle.getLastWakeReason() - == PowerManager.WAKE_REASON_POWER_BUTTON; - return lastWakeupFromPowerButton - && mWakefulnessLifecycle.getLastWakeTime() != UNKNOWN_LAST_WAKE_TIME - && mSystemClock.uptimeMillis() - mWakefulnessLifecycle.getLastWakeTime() - < RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS; - } - - private void vibrateError(BiometricSourceType type) { - if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - mVibratorHelper.performHapticFeedback( - mKeyguardViewController.getViewRootImpl().getView(), - HapticFeedbackConstants.REJECT - ); - } else { - mVibratorHelper.vibrateAuthError( - getClass().getSimpleName() + ", type =" + type + "device-entry::error"); - } - } - private void cleanup() { releaseBiometricWakeLock(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java index 92c786fb569f..00fd9fbfffe3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -263,8 +263,7 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen if (result.success) { mCached = true; mCache = result.bitmap; - mMediaManager.updateMediaMetaData( - true /* metaDataChanged */, true /* allowEnterAnimation */); + mMediaManager.updateMediaMetaData(true /* metaDataChanged */); } mLoader = null; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 3adf3385e3cc..400ac7b415b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -961,7 +961,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN); } if (isShowing) { - mMediaManager.updateMediaMetaData(false, animate && !isOccluded); + mMediaManager.updateMediaMetaData(false); } mNotificationShadeWindowController.setKeyguardOccluded(isOccluded); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 2d14f6b3c508..57a8e6fe0d91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -221,7 +221,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu @Override public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { - mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation); + mMediaManager.updateMediaMetaData(metaDataChanged); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt index e9e52a2397e1..1670dd39ba24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt @@ -104,7 +104,7 @@ constructor( val callback = object : WifiPickerTracker.WifiPickerTrackerCallback { override fun onWifiEntriesChanged() { - val connectedEntry = wifiPickerTracker?.connectedWifiEntry + val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection logOnWifiEntriesChanged(connectedEntry) val secondaryNetworks = @@ -217,6 +217,21 @@ constructor( .stateIn(scope, SharingStarted.Eagerly, emptyList()) /** + * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the + * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry + * if it exists, falling back on the connected entry if null + */ + private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry? + get() { + val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry + return if (mergedEntry != null && mergedEntry.isDefaultNetwork) { + mergedEntry + } else { + this?.connectedWifiEntry + } + } + + /** * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the * primary network. Returns an inactive network if it's not primary. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 7c96029cfe15..ceed81a182aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -657,7 +657,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mEditText.setText(mEntry.remoteInputText); mEditText.setSelection(mEditText.length()); mEditText.requestFocus(); - mController.addRemoteInput(mEntry, mToken); + mController.addRemoteInput(mEntry, mToken, "RemoteInputView#focus"); setAttachment(mEntry.remoteInputAttachment); updateSendButton(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt index 3df9cbb29e4a..91409a376556 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt @@ -11,11 +11,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.communal.data.model.CommunalWidgetMetadata +import com.android.systemui.communal.shared.CommunalContentSize import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.FakeLogBuffer +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.kotlinArgumentCaptor @@ -59,9 +62,12 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo + private lateinit var communalRepository: FakeCommunalRepository + private lateinit var logBuffer: LogBuffer private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) @Before @@ -71,6 +77,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { logBuffer = FakeLogBuffer.Factory.create() featureFlagEnabled(true) + communalRepository = FakeCommunalRepository() + communalRepository.setIsCommunalEnabled(true) + + overrideResource( + R.array.config_communalWidgetAllowlist, + arrayOf(componentName1, componentName2) + ) + whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch") whenever(userTracker.userHandle).thenReturn(userHandle) } @@ -219,11 +233,36 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { Mockito.verify(appWidgetHost).stopListening() } + @Test + fun getCommunalWidgetAllowList_onInit() { + testScope.runTest { + val repository = initCommunalWidgetRepository() + val communalWidgetAllowlist = repository.communalWidgetAllowlist + assertThat( + listOf( + CommunalWidgetMetadata( + componentName = componentName1, + priority = 2, + sizes = listOf(CommunalContentSize.HALF) + ), + CommunalWidgetMetadata( + componentName = componentName2, + priority = 1, + sizes = listOf(CommunalContentSize.HALF) + ) + ) + ) + .containsExactly(*communalWidgetAllowlist.toTypedArray()) + } + } + private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl { return CommunalWidgetRepositoryImpl( + context, appWidgetManager, appWidgetHost, broadcastDispatcher, + communalRepository, packageManager, userManager, userTracker, @@ -282,4 +321,9 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { private fun installedProviders(providers: List<AppWidgetProviderInfo>) { whenever(appWidgetManager.installedProviders).thenReturn(providers) } + + companion object { + const val componentName1 = "component name 1" + const val componentName2 = "component name 2" + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt new file mode 100644 index 000000000000..9b8e581d1ba4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt @@ -0,0 +1,195 @@ +/* + * 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.systemui.deviceentry.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.logging.BiometricUnlockLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor +import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository +import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.statusbar.phone.ScreenOffAnimationController +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceEntryHapticsInteractorTest : SysuiTestCase() { + + private lateinit var repository: DeviceEntryHapticsRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + private lateinit var keyEventRepository: FakeKeyEventRepository + private lateinit var powerRepository: FakePowerRepository + private lateinit var systemClock: FakeSystemClock + private lateinit var underTest: DeviceEntryHapticsInteractor + + @Before + fun setUp() { + repository = DeviceEntryHapticsRepositoryImpl() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + keyEventRepository = FakeKeyEventRepository() + powerRepository = FakePowerRepository() + systemClock = FakeSystemClock() + underTest = + DeviceEntryHapticsInteractor( + repository = repository, + fingerprintPropertyRepository = fingerprintPropertyRepository, + biometricSettingsRepository = biometricSettingsRepository, + keyEventInteractor = KeyEventInteractor(keyEventRepository), + powerInteractor = + PowerInteractor( + powerRepository, + mock(FalsingCollector::class.java), + mock(ScreenOffAnimationController::class.java), + mock(StatusBarStateController::class.java), + ), + systemClock = systemClock, + logger = mock(BiometricUnlockLogger::class.java), + ) + } + + @Test + fun nonPowerButtonFPS_vibrateSuccess() = runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) + underTest.vibrateSuccess() + assertThat(playSuccessHaptic).isTrue() + } + + @Test + fun powerButtonFPS_vibrateSuccess() = runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + keyEventRepository.setPowerButtonDown(false) + + // It's been 10 seconds since the last power button wakeup + setAwakeFromPowerButton() + runCurrent() + systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000) + + underTest.vibrateSuccess() + assertThat(playSuccessHaptic).isTrue() + } + + @Test + fun powerButtonFPS_powerDown_doNotVibrateSuccess() = runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + keyEventRepository.setPowerButtonDown(true) // power button is currently DOWN + + // It's been 10 seconds since the last power button wakeup + setAwakeFromPowerButton() + runCurrent() + systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000) + + underTest.vibrateSuccess() + assertThat(playSuccessHaptic).isFalse() + } + + @Test + fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + keyEventRepository.setPowerButtonDown(false) + + // It's only been 50ms since the last power button wakeup + setAwakeFromPowerButton() + runCurrent() + systemClock.setUptimeMillis(systemClock.uptimeMillis() + 50) + + underTest.vibrateSuccess() + assertThat(playSuccessHaptic).isFalse() + } + + @Test + fun nonPowerButtonFPS_vibrateError() = runTest { + val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) + setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) + underTest.vibrateError() + assertThat(playErrorHaptic).isTrue() + } + + @Test + fun powerButtonFPS_vibrateError() = runTest { + val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + underTest.vibrateError() + assertThat(playErrorHaptic).isTrue() + } + + @Test + fun powerButtonFPS_powerDown_doNotVibrateError() = runTest { + val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + keyEventRepository.setPowerButtonDown(true) + underTest.vibrateError() + assertThat(playErrorHaptic).isFalse() + } + + private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) { + fingerprintPropertyRepository.setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = fingerprintSensorType, + sensorLocations = mapOf(), + ) + } + + private fun setPowerButtonFingerprintProperty() { + setFingerprintSensorType(FingerprintSensorType.POWER_BUTTON) + } + + private fun setFingerprintEnrolled() { + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + } + + private fun setAwakeFromPowerButton() { + powerRepository.updateWakefulness( + WakefulnessState.AWAKE, + WakeSleepReason.POWER_BUTTON, + WakeSleepReason.POWER_BUTTON, + powerButtonLaunchGestureTriggered = false, + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt index 0ee348e0d23d..7750d25de753 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt @@ -28,6 +28,8 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock +import kotlin.math.max +import kotlin.test.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -149,26 +151,52 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { } @Test + fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() { + // GIVEN max velocity and a slider progress at half progress + val firstProgress = 0.5f + val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress) + + // Given a second slider progress event smaller than the progress threshold + val secondProgress = firstProgress + max(0f, config.deltaProgressForDragThreshold - 0.01f) + + // GIVEN system running for 1s + clock.advanceTime(1000) + + // WHEN two calls to play occur with the required threshold separation (time and progress) + sliderHapticFeedbackProvider.onProgress(firstProgress) + clock.advanceTime(dragTextureThresholdMillis.toLong()) + sliderHapticFeedbackProvider.onProgress(secondProgress) + + // THEN Only the first compositions plays + verify(vibratorHelper, times(1)) + .vibrate(eq(firstTicks), any(VibrationAttributes::class.java)) + verify(vibratorHelper, times(1)) + .vibrate(any(VibrationEffect::class.java), any(VibrationAttributes::class.java)) + } + + @Test fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() { - // GIVEN max velocity and slider progress - val progress = 1f - val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress) - val ticks = VibrationEffect.startComposition() - repeat(config.numberOfLowTicks) { - ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale) - } + // GIVEN max velocity and a slider progress at half progress + val firstProgress = 0.5f + val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress) + + // Given a second slider progress event beyond progress threshold + val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f + val secondTicks = generateTicksComposition(config.maxVelocityToScale, secondProgress) // GIVEN system running for 1s clock.advanceTime(1000) - // WHEN two calls to play occur with the required threshold separation - sliderHapticFeedbackProvider.onProgress(progress) + // WHEN two calls to play occur with the required threshold separation (time and progress) + sliderHapticFeedbackProvider.onProgress(firstProgress) clock.advanceTime(dragTextureThresholdMillis.toLong()) - sliderHapticFeedbackProvider.onProgress(progress) + sliderHapticFeedbackProvider.onProgress(secondProgress) - // THEN the correct composition plays two times - verify(vibratorHelper, times(2)) - .vibrate(eq(ticks.compose()), any(VibrationAttributes::class.java)) + // THEN the correct compositions play + verify(vibratorHelper, times(1)) + .vibrate(eq(firstTicks), any(VibrationAttributes::class.java)) + verify(vibratorHelper, times(1)) + .vibrate(eq(secondTicks), any(VibrationAttributes::class.java)) } @Test @@ -229,6 +257,38 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { .vibrate(eq(bookendVibration), any(VibrationAttributes::class.java)) } + fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() { + // GIVEN max velocity and a slider progress at half progress + val progress = 0.5f + + // GIVEN system running for 1s + clock.advanceTime(1000) + + // WHEN a drag texture plays + sliderHapticFeedbackProvider.onProgress(progress) + + // THEN the dragTextureLastProgress remembers the latest progress + assertEquals(progress, sliderHapticFeedbackProvider.dragTextureLastProgress) + } + + @Test + fun dragTextureLastProgress_afterDragTextureHaptics_resetsOnHandleReleased() { + // GIVEN max velocity and a slider progress at half progress + val progress = 0.5f + + // GIVEN system running for 1s + clock.advanceTime(1000) + + // WHEN a drag texture plays + sliderHapticFeedbackProvider.onProgress(progress) + + // WHEN the handle is released + sliderHapticFeedbackProvider.onHandleReleasedFromTouch() + + // THEN the dragTextureLastProgress tracker is reset + assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress) + } + private fun scaleAtBookends(velocity: Float): Float { val range = config.upperBookendScale - config.lowerBookendScale val interpolatedVelocity = @@ -244,4 +304,15 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { val bump = interpolatedVelocity * config.additionalVelocityMaxBump return interpolatedProgress * range + config.progressBasedDragMinScale + bump } + + private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect { + val ticks = VibrationEffect.startComposition() + repeat(config.numberOfLowTicks) { + ticks.addPrimitive( + VibrationEffect.Composition.PRIMITIVE_LOW_TICK, + scaleAtProgressChange(velocity, progress) + ) + } + return ticks.compose() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt index d8cdf29284ed..90fd6523ec12 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt @@ -147,7 +147,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { emptyMap() ) verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture()) - verify(authController, atLeastOnce()).addCallback(authControllerCallback.capture()) + verify(authController, times(2)).addCallback(authControllerCallback.capture()) } @Test @@ -314,18 +314,18 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { fun faceEnrollmentChangeIsPropagatedForTheCurrentUser() = testScope.runTest { createBiometricSettingsRepository() + val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled) + faceAuthIsEnabledByBiometricManager() doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID) runCurrent() - clearInvocations(authController) - whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false) - val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled) + enrollmentChange(FACE, PRIMARY_USER_ID, false) assertThat(faceAuthAllowed()).isFalse() - verify(authController).addCallback(authControllerCallback.capture()) + enrollmentChange(REAR_FINGERPRINT, PRIMARY_USER_ID, true) assertThat(faceAuthAllowed()).isFalse() @@ -375,25 +375,20 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { fun faceEnrollmentChangesArePropagatedAfterUserSwitch() = testScope.runTest { createBiometricSettingsRepository() + val faceAuthAllowed by collectLastValue(underTest.isFaceAuthEnrolledAndEnabled) + verify(biometricManager) .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture()) userRepository.setSelectedUserInfo(ANOTHER_USER) doNotDisableKeyguardAuthFeatures(ANOTHER_USER_ID) biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID) - - runCurrent() - clearInvocations(authController) - - val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled) - runCurrent() - - verify(authController).addCallback(authControllerCallback.capture()) - + onNonStrongAuthChanged(true, ANOTHER_USER_ID) whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true) enrollmentChange(FACE, ANOTHER_USER_ID, true) + runCurrent() - assertThat(faceAuthAllowed()).isTrue() + assertThat(faceAuthAllowed).isTrue() } @Test @@ -637,6 +632,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed) faceAuthIsEnrolled() + enrollmentChange(FACE, PRIMARY_USER_ID, true) deviceIsInPostureThatSupportsFaceAuth() doNotDisableKeyguardAuthFeatures() faceAuthIsStrongBiometric() @@ -660,6 +656,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed) faceAuthIsEnrolled() + enrollmentChange(FACE, PRIMARY_USER_ID, true) deviceIsInPostureThatSupportsFaceAuth() doNotDisableKeyguardAuthFeatures() faceAuthIsNonStrongBiometric() @@ -753,7 +750,9 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { } private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) { - authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled) + authControllerCallback.allValues.forEach { + it.onEnrollmentsChanged(biometricType, userId, enabled) + } } private fun doNotDisableKeyguardAuthFeatures(userId: Int = PRIMARY_USER_ID) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index 06eb0dd364b5..6ad2d731f375 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -43,7 +43,7 @@ import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.DismissCallbackRegistry -import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -95,6 +95,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { FakeDeviceEntryFingerprintAuthRepository private lateinit var fakeKeyguardRepository: FakeKeyguardRepository private lateinit var powerInteractor: PowerInteractor + private lateinit var fakeBiometricSettingsRepository: FakeBiometricSettingsRepository @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig @@ -123,6 +124,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { facePropertyRepository = FakeFacePropertyRepository() fakeKeyguardRepository = FakeKeyguardRepository() powerInteractor = PowerInteractorFactory.create().powerInteractor + fakeBiometricSettingsRepository = FakeBiometricSettingsRepository() + underTest = SystemUIKeyguardFaceAuthInteractor( mContext, @@ -147,7 +150,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { mock(StatusBarStateController::class.java), mock(KeyguardStateController::class.java), bouncerRepository, - mock(BiometricSettingsRepository::class.java), + fakeBiometricSettingsRepository, FakeSystemClock(), keyguardUpdateMonitor, ), @@ -160,6 +163,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { facePropertyRepository, faceWakeUpTriggersConfig, powerInteractor, + fakeBiometricSettingsRepository, ) } @@ -481,6 +485,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { fun faceUnlockIsDisabledWhenFpIsLockedOut() = testScope.runTest { underTest.start() + fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true) fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt index 34360d2ddd5c..6cdf4efd67da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt @@ -4,7 +4,9 @@ import android.content.ComponentName import android.os.UserHandle import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED import com.android.systemui.SysuiTestCase +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader @@ -20,6 +22,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.never import org.mockito.Mockito.verify @RunWith(AndroidTestingRunner::class) @@ -37,10 +40,11 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { private val view: MediaProjectionAppSelectorView = mock() private val policyResolver: ScreenCaptureDevicePolicyResolver = mock() + private val logger = mock<MediaProjectionMetricsLogger>() private val thumbnailLoader = FakeThumbnailLoader() - private val controller = + private fun createController(isFirstStart: Boolean = true) = MediaProjectionAppSelectorController( taskListProvider, view, @@ -50,6 +54,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { appSelectorComponentName, callerPackageName, thumbnailLoader, + isFirstStart, + logger ) @Before @@ -61,7 +67,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { fun initNoRecentTasks_bindsEmptyList() { taskListProvider.tasks = emptyList() - controller.init() + createController().init() verify(view).bind(emptyList()) } @@ -70,7 +76,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { fun initOneRecentTask_bindsList() { taskListProvider.tasks = listOf(createRecentTask(taskId = 1)) - controller.init() + createController().init() verify(view).bind(listOf(createRecentTask(taskId = 1))) } @@ -86,7 +92,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ) taskListProvider.tasks = tasks - controller.init() + createController().init() assertThat(thumbnailLoader.capturedTaskIds).containsExactly(2, 3) } @@ -101,7 +107,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ) taskListProvider.tasks = tasks - controller.init() + createController().init() verify(view) .bind( @@ -124,7 +130,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ) taskListProvider.tasks = tasks - controller.init() + createController().init() verify(view) .bind( @@ -147,7 +153,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ) taskListProvider.tasks = tasks - controller.init() + createController().init() verify(view) .bind( @@ -172,7 +178,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ) taskListProvider.tasks = tasks - controller.init() + createController().init() verify(view) .bind( @@ -199,11 +205,29 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { taskListProvider.tasks = tasks givenCaptureAllowed(isAllow = false) - controller.init() + createController().init() verify(view).bind(emptyList()) } + @Test + fun init_firstStart_logsAppSelectorDisplayed() { + val controller = createController(isFirstStart = true) + + controller.init() + + verify(logger).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) + } + + @Test + fun init_notFirstStart_doesNotLogAppSelectorDisplayed() { + val controller = createController(isFirstStart = false) + + controller.init() + + verify(logger, never()).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) + } + private fun givenCaptureAllowed(isAllow: Boolean) { whenever(policyResolver.isScreenCaptureAllowed(any(), any())).thenReturn(isAllow) } @@ -216,6 +240,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { ): RecentTask { return RecentTask( taskId = taskId, + displayId = 0, topActivityComponent = topActivityComponent, baseIntentComponent = ComponentName("com", "Test"), userId = userId, diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt index 2c7ee56e9408..d75553fe57ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt @@ -128,6 +128,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { private fun createRecentTask(taskId: Int): RecentTask = RecentTask( taskId = taskId, + displayId = 0, userId = 0, topActivityComponent = null, baseIntentComponent = null, diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index 6e6833dc0944..90d2e78a411f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -17,8 +17,10 @@ package com.android.systemui.screenrecord; import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; @@ -37,6 +39,8 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; +import com.android.systemui.mediaprojection.SessionCreationSource; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; import com.android.systemui.plugins.ActivityStarter; @@ -76,6 +80,8 @@ public class RecordingControllerTest extends SysuiTestCase { private ActivityStarter mActivityStarter; @Mock private UserTracker mUserTracker; + @Mock + private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; private FakeFeatureFlags mFeatureFlags; private RecordingController mController; @@ -86,8 +92,15 @@ public class RecordingControllerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); mFeatureFlags = new FakeFeatureFlags(); - mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, mContext, - mFeatureFlags, mUserContextProvider, () -> mDevicePolicyResolver, mUserTracker); + mController = new RecordingController( + mMainExecutor, + mBroadcastDispatcher, + mContext, + mFeatureFlags, + mUserContextProvider, + () -> mDevicePolicyResolver, + mUserTracker, + mMediaProjectionMetricsLogger); mController.addCallback(mCallback); } @@ -269,4 +282,21 @@ public class RecordingControllerTest extends SysuiTestCase { assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class); } + + @Test + public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true); + mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true); + when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false); + + mController.createScreenRecordDialog(mContext, mFeatureFlags, + mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null); + + verify(mMediaProjectionMetricsLogger) + .notifyProjectionInitiated(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt deleted file mode 100644 index e4da53a1a0a4..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar - -import com.google.common.truth.Truth.assertThat - -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Point -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -private const val WIDTH = 200 -private const val HEIGHT = 200 - -@RunWith(AndroidTestingRunner::class) -@SmallTest -class MediaArtworkProcessorTest : SysuiTestCase() { - - private var screenWidth = 0 - private var screenHeight = 0 - - private lateinit var processor: MediaArtworkProcessor - - @Before - fun setUp() { - processor = MediaArtworkProcessor() - - val point = Point() - checkNotNull(context.display).getSize(point) - screenWidth = point.x - screenHeight = point.y - } - - @After - fun tearDown() { - processor.clearCache() - } - - @Test - fun testProcessArtwork() { - // GIVEN some "artwork", which is just a solid blue image - val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) - Canvas(artwork).drawColor(Color.BLUE) - // WHEN the background is created from the artwork - val background = processor.processArtwork(context, artwork)!! - // THEN the background has the size of the screen that has been downsamples - assertThat(background.height).isLessThan(screenHeight) - assertThat(background.width).isLessThan(screenWidth) - assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888) - } - - @Test - fun testCache() { - // GIVEN a solid blue image - val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) - Canvas(artwork).drawColor(Color.BLUE) - // WHEN the background is processed twice - val background1 = processor.processArtwork(context, artwork)!! - val background2 = processor.processArtwork(context, artwork)!! - // THEN the two bitmaps are the same - // Note: This is currently broken and trying to use caching causes issues - assertThat(background1).isNotSameInstanceAs(background2) - } - - @Test - fun testConfig() { - // GIVEN some which is not ARGB_8888 - val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ALPHA_8) - Canvas(artwork).drawColor(Color.BLUE) - // WHEN the background is created from the artwork - val background = processor.processArtwork(context, artwork)!! - // THEN the background has Config ARGB_8888 - assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888) - } - - @Test - fun testRecycledArtwork() { - // GIVEN some "artwork", which is just a solid blue image - val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) - Canvas(artwork).drawColor(Color.BLUE) - // AND the artwork is recycled - artwork.recycle() - // WHEN the background is created from the artwork - val background = processor.processArtwork(context, artwork) - // THEN the processed bitmap is null - assertThat(background).isNull() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt index 9d6ea857fefc..cfcf4257ce28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt @@ -48,9 +48,7 @@ class NotificationMediaManagerTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - doCallRealMethod() - .whenever(notificationMediaManager) - .updateMediaMetaData(anyBoolean(), anyBoolean()) + doCallRealMethod().whenever(notificationMediaManager).updateMediaMetaData(anyBoolean()) doReturn(mockBackDropView).whenever(notificationMediaManager).backDropView } @@ -62,7 +60,7 @@ class NotificationMediaManagerTest : SysuiTestCase() { notificationMediaManager.mIsLockscreenLiveWallpaperEnabled = true for (metaDataChanged in listOf(true, false)) { for (allowEnterAnimation in listOf(true, false)) { - notificationMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation) + notificationMediaManager.updateMediaMetaData(metaDataChanged) verify(notificationMediaManager, never()).mediaMetadata } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java deleted file mode 100644 index aeb5b037be0c..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.notification; - -import static com.google.common.truth.Truth.assertThat; - -import android.annotation.Nullable; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.test.suitebuilder.annotation.SmallTest; - -import androidx.palette.graphics.Palette; -import androidx.test.runner.AndroidJUnit4; - -import com.android.systemui.SysuiTestCase; - -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class MediaNotificationProcessorTest extends SysuiTestCase { - - private static final int BITMAP_WIDTH = 10; - private static final int BITMAP_HEIGHT = 10; - - /** - * Color tolerance is borrowed from the AndroidX test utilities for Palette. - */ - private static final int COLOR_TOLERANCE = 8; - - @Nullable private Bitmap mArtwork; - - @After - public void tearDown() { - if (mArtwork != null) { - mArtwork.recycle(); - mArtwork = null; - } - } - - @Test - public void findBackgroundSwatch_white() { - // Given artwork that is completely white. - mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(mArtwork); - canvas.drawColor(Color.WHITE); - // WHEN the background swatch is computed - Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork); - // THEN the swatch color is white - assertCloseColors(swatch.getRgb(), Color.WHITE); - } - - @Test - public void findBackgroundSwatch_red() { - // Given artwork that is completely red. - mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(mArtwork); - canvas.drawColor(Color.RED); - // WHEN the background swatch is computed - Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork); - // THEN the swatch color is red - assertCloseColors(swatch.getRgb(), Color.RED); - } - - static void assertCloseColors(int expected, int actual) { - assertThat((float) Color.red(expected)).isWithin(COLOR_TOLERANCE).of(Color.red(actual)); - assertThat((float) Color.green(expected)).isWithin(COLOR_TOLERANCE).of(Color.green(actual)); - assertThat((float) Color.blue(expected)).isWithin(COLOR_TOLERANCE).of(Color.blue(actual)); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 700de5305778..8344cd143c5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -18,7 +18,9 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; + import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -38,7 +40,6 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableResources; -import android.view.HapticFeedbackConstants; import android.view.ViewRootImpl; import com.android.internal.logging.MetricsLogger; @@ -47,6 +48,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.logging.BiometricUnlockLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; @@ -122,6 +124,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { private BiometricUnlockLogger mLogger; @Mock private ViewRootImpl mViewRootImpl; + @Mock + private DeviceEntryHapticsInteractor mDeviceEntryHapticsInteractor; private final FakeSystemClock mSystemClock = new FakeSystemClock(); private FakeFeatureFlags mFeatureFlags; private BiometricUnlockController mBiometricUnlockController; @@ -158,7 +162,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mAuthController, mStatusBarStateController, mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper, mSystemClock, - mFeatureFlags + mFeatureFlags, + mDeviceEntryHapticsInteractor ); biometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); biometricUnlockController.addListener(mBiometricUnlockEventsListener); @@ -462,145 +467,23 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { } @Test - public void onSideFingerprintSuccess_recentPowerButtonPress_noHaptic() { - // GIVEN side fingerprint enrolled, last wake reason was power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - - // GIVEN last wake time just occurred - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - - // WHEN biometric fingerprint succeeds - givenFingerprintModeUnlockCollapsing(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, - true); - - // THEN DO NOT vibrate the device - verify(mVibratorHelper, never()).vibrateAuthSuccess(anyString()); - } - - @Test - public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() { - // GIVEN side fingerprint enrolled, last wake reason was power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - - // GIVEN last wake time was 500ms ago - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - mSystemClock.advanceTime(500); - - // WHEN biometric fingerprint succeeds - givenFingerprintModeUnlockCollapsing(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, - true); - - // THEN vibrate the device - verify(mVibratorHelper).vibrateAuthSuccess(anyString()); - } - - @Test - public void onSideFingerprintSuccess_oldPowerButtonPress_playOneWayHaptic() { - // GIVEN oneway haptics is enabled - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - // GIVEN side fingerprint enrolled, last wake reason was power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - - // GIVEN last wake time was 500ms ago - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - mSystemClock.advanceTime(500); - + public void onFingerprintSuccess_requestSuccessHaptic() { // WHEN biometric fingerprint succeeds givenFingerprintModeUnlockCollapsing(); mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true); - // THEN vibrate the device - verify(mVibratorHelper).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.CONFIRM) - ); - } - - @Test - public void onSideFingerprintSuccess_recentGestureWakeUp_playHaptic() { - // GIVEN side fingerprint enrolled, wakeup just happened - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - - // GIVEN last wake reason was from a gesture - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_GESTURE); - - // WHEN biometric fingerprint succeeds - givenFingerprintModeUnlockCollapsing(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, - true); - - // THEN vibrate the device - verify(mVibratorHelper).vibrateAuthSuccess(anyString()); - } - - @Test - public void onSideFingerprintSuccess_recentGestureWakeUp_playOnewayHaptic() { - //GIVEN oneway haptics is enabled - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - // GIVEN side fingerprint enrolled, wakeup just happened - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - - // GIVEN last wake reason was from a gesture - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_GESTURE); - - // WHEN biometric fingerprint succeeds - givenFingerprintModeUnlockCollapsing(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, - true); - - // THEN vibrate the device - verify(mVibratorHelper).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.CONFIRM) - ); - } - - @Test - public void onSideFingerprintFail_alwaysPlaysHaptic() { - // GIVEN side fingerprint enrolled, last wake reason was recent power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - - // WHEN biometric fingerprint fails - mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); - // THEN always vibrate the device - verify(mVibratorHelper).vibrateAuthError(anyString()); + verify(mDeviceEntryHapticsInteractor).vibrateSuccess(); } @Test - public void onSideFingerprintFail_alwaysPlaysOneWayHaptic() { - // GIVEN oneway haptics is enabled - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - // GIVEN side fingerprint enrolled, last wake reason was recent power button - when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - when(mWakefulnessLifecycle.getLastWakeReason()) - .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - + public void onFingerprintFail_requestErrorHaptic() { // WHEN biometric fingerprint fails mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); // THEN always vibrate the device - verify(mVibratorHelper).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.REJECT) - ); + verify(mDeviceEntryHapticsInteractor).vibrateError(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt index c2f56654e00a..31263627213d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt @@ -201,11 +201,11 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.isWifiDefault) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() assertThat(latest).isTrue() @@ -229,11 +229,11 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.isWifiDefault) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(false) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() assertThat(latest).isFalse() @@ -526,13 +526,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) whenever(this.subscriptionId).thenReturn(567) + whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() @@ -546,11 +547,12 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) whenever(wifiManager.maxSignalLevel).thenReturn(5) getCallback().onWifiEntriesChanged() @@ -566,12 +568,13 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) + whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() @@ -628,11 +631,12 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(false) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) getCallback().onWifiEntriesChanged() assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) @@ -717,12 +721,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiEntry = + val mergedEntry = mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) + whenever(this.isDefaultNetwork).thenReturn(true) } - whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) getCallback().onWifiEntriesChanged() assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() @@ -730,6 +736,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { // WHEN we lose our current network whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(null) getCallback().onWifiEntriesChanged() // THEN we update to no network @@ -767,6 +774,56 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { } @Test + fun wifiNetwork_carrierMerged_default_usesCarrierMergedInfo() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val mergedEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(3) + whenever(this.isDefaultNetwork).thenReturn(true) + } + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(1) + whenever(this.title).thenReturn(TITLE) + } + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + + getCallback().onWifiEntriesChanged() + + assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() + } + + @Test + fun wifiNetwork_carrierMerged_notDefault_usesConnectedInfo() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val mergedEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(3) + whenever(this.isDefaultNetwork).thenReturn(false) + } + val wifiEntry = + mock<WifiEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(1) + whenever(this.title).thenReturn(TITLE) + } + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + + getCallback().onWifiEntriesChanged() + + assertThat(latest is WifiNetworkModel.Active).isTrue() + } + + @Test fun secondaryNetworks_activeEntriesEmpty_isEmpty() = testScope.runTest { featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt index 1a8c5830e453..30132f7747b7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt @@ -1,5 +1,6 @@ package com.android.systemui.communal.data.repository +import com.android.systemui.communal.data.model.CommunalWidgetMetadata import com.android.systemui.communal.shared.CommunalAppWidgetInfo import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -8,6 +9,7 @@ import kotlinx.coroutines.flow.MutableStateFlow class FakeCommunalWidgetRepository : CommunalWidgetRepository { private val _stopwatchAppWidgetInfo = MutableStateFlow<CommunalAppWidgetInfo?>(null) override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo + override var communalWidgetAllowlist: List<CommunalWidgetMetadata> = emptyList() fun setStopwatchAppWidgetInfo(appWidgetInfo: CommunalAppWidgetInfo) { _stopwatchAppWidgetInfo.value = appWidgetInfo diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt index e91e9559fa1e..852611230623 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt @@ -34,7 +34,7 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository { get() = _isFingerprintAuthCurrentlyAllowed private val _isFaceAuthEnrolledAndEnabled = MutableStateFlow(false) - override val isFaceAuthEnrolledAndEnabled: Flow<Boolean> + override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean> get() = _isFaceAuthEnrolledAndEnabled private val _isFaceAuthCurrentlyAllowed = MutableStateFlow(false) diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index ee41a69c6f4c..65975e44ee2a 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -1437,6 +1437,10 @@ public class ContentCaptureManagerService extends if (!mDevCfgEnableContentProtectionReceiver) { return false; } + if (mDevCfgContentProtectionRequiredGroups.isEmpty() + && mDevCfgContentProtectionOptionalGroups.isEmpty()) { + return false; + } } return mContentProtectionConsentManager.isConsentGranted(userId) && mContentProtectionBlocklistManager.isAllowed(packageName); diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 8df54569cccd..638abdba36ec 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -1387,11 +1387,12 @@ public abstract class PackageManagerInternal { @UserIdInt int userId); /** - * Tells PackageManager when a component (except BroadcastReceivers) of the package is used + * Tells PackageManager when a component of the package is used * and the package should get out of stopped state and be enabled. */ public abstract void notifyComponentUsed(@NonNull String packageName, - @UserIdInt int userId, @NonNull String recentCallingPackage, @NonNull String debugInfo); + @UserIdInt int userId, @Nullable String recentCallingPackage, + @NonNull String debugInfo); /** @deprecated For legacy shell command only. */ @Deprecated diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 5fb889a23fc5..1650a96a4012 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -5309,7 +5309,7 @@ public class AccountManagerService if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName); } - long flags = Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS | Context.BIND_AUTO_CREATE; + long flags = Context.BIND_AUTO_CREATE; if (mAuthenticatorCache.getBindInstantServiceAllowed(mAccounts.userId)) { flags |= Context.BIND_ALLOW_INSTANT; } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 0956c6ded013..5f1a7e7e8123 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -3678,8 +3678,8 @@ public final class ActiveServices { || (flags & Context.BIND_EXTERNAL_SERVICE_LONG) != 0; final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0; final boolean inSharedIsolatedProcess = (flags & Context.BIND_SHARED_ISOLATED_PROCESS) != 0; - final boolean filterOutQuarantined = - (flags & Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS) != 0; + final boolean matchQuarantined = + (flags & Context.BIND_MATCH_QUARANTINED_COMPONENTS) != 0; ProcessRecord attributedApp = null; if (sdkSandboxClientAppUid > 0) { @@ -3689,7 +3689,7 @@ public final class ActiveServices { isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg, isBindExternal, allowInstant, null /* fgsDelegateOptions */, - inSharedIsolatedProcess, filterOutQuarantined); + inSharedIsolatedProcess, matchQuarantined); if (res == null) { return 0; } @@ -4202,7 +4202,7 @@ public final class ActiveServices { sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal, allowInstant, fgsDelegateOptions, inSharedIsolatedProcess, - false /* filterOutQuarantined */); + false /* matchQuarantined */); } private ServiceLookupResult retrieveServiceLocked(Intent service, @@ -4211,7 +4211,7 @@ public final class ActiveServices { String callingPackage, int callingPid, int callingUid, int userId, boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal, boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions, - boolean inSharedIsolatedProcess, boolean filterOutQuarantined) { + boolean inSharedIsolatedProcess, boolean matchQuarantined) { if (isSdkSandboxService && instanceName == null) { throw new IllegalArgumentException("No instanceName provided for sdk sandbox process"); } @@ -4333,8 +4333,8 @@ public final class ActiveServices { if (allowInstant) { flags |= PackageManager.MATCH_INSTANT; } - if (filterOutQuarantined) { - flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; + if (matchQuarantined) { + flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS; } // TODO: come back and remove this assumption to triage all services ResolveInfo rInfo = mAm.getPackageManagerInternal().resolveService(service, diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b43b986064fe..31817f1c427d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -58,7 +58,6 @@ import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_INSTRUMENTAT import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PERSISTENT; import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_SYSTEM; import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT; -import static android.content.pm.PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES; import static android.content.pm.PackageManager.MATCH_ALL; import static android.content.pm.PackageManager.MATCH_ANY_USER; @@ -14295,8 +14294,7 @@ public class ActivityManagerService extends IActivityManager.Stub private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType, int callingUid, int[] users, int[] broadcastAllowList) { // TODO: come back and remove this assumption to triage all broadcasts - long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING - | FILTER_OUT_QUARANTINED_COMPONENTS; + long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING; List<ResolveInfo> receivers = null; HashSet<ComponentName> singleUserReceivers = null; diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index 127c5b389d79..3c56752d08e3 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -1440,10 +1440,9 @@ public class BroadcastQueueImpl extends BroadcastQueue { r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED); } - // Broadcast is being executed, its package can't be stopped. try { - mService.mPackageManagerInt.setPackageStoppedState( - r.curComponent.getPackageName(), false, r.userId); + mService.mPackageManagerInt.notifyComponentUsed( + r.curComponent.getPackageName(), r.userId, r.callerPackage, r.toString()); } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " + r.curComponent.getPackageName() + ": " + e); diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index d19eae5b0709..b48169788180 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -1982,8 +1982,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mService.notifyPackageUse(receiverPackageName, PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER); - mService.mPackageManagerInt.setPackageStoppedState( - receiverPackageName, false, r.userId); + mService.mPackageManagerInt.notifyComponentUsed( + receiverPackageName, r.userId, r.callerPackage, r.toString()); } private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app, diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 3771c05a294e..f02b8c737f90 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -511,8 +511,8 @@ class ProcessRecord implements WindowProcessListener { pw.print(prefix); pw.print("pid="); pw.println(mPid); pw.print(prefix); pw.print("lastActivityTime="); TimeUtils.formatDuration(mLastActivityTime, nowUptime, pw); - pw.print(prefix); pw.print("startUptimeTime="); - TimeUtils.formatDuration(mStartElapsedTime, nowUptime, pw); + pw.print(prefix); pw.print("startUpTime="); + TimeUtils.formatDuration(mStartUptime, nowUptime, pw); pw.print(prefix); pw.print("startElapsedTime="); TimeUtils.formatDuration(mStartElapsedTime, nowElapsedTime, pw); pw.println(); diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 16e3fdf2a6ab..2d231b3cf42e 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -152,6 +152,7 @@ public class SettingsToPropertiesMapper { "preload_safety", "responsible_apis", "rust", + "safety_center", "system_performance", "test_suites", "text", @@ -159,7 +160,14 @@ public class SettingsToPropertiesMapper { "tv_system_ui", "vibrator", "virtual_devices", + "wear_calling_messaging", + "wear_connectivity", + "wear_esim_carriers", "wear_frameworks", + "wear_health_services", + "wear_media", + "wear_offload", + "wear_security", "wear_system_health", "wear_systems", "window_surfaces", diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 8736a53bb9f5..ac7d9c171247 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -221,9 +221,8 @@ public class SyncManager { /** Flags used when connecting to a sync adapter service */ private static final Context.BindServiceFlags SYNC_ADAPTER_CONNECTION_FLAGS = - Context.BindServiceFlags.of( - Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS | Context.BIND_AUTO_CREATE - | Context.BIND_NOT_FOREGROUND | Context.BIND_ALLOW_OOM_MANAGEMENT); + Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND + | Context.BIND_ALLOW_OOM_MANAGEMENT); /** Singleton instance. */ @GuardedBy("SyncManager.class") diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 0689478ded1e..5b87eeac938d 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -494,6 +494,7 @@ public final class DisplayManagerService extends SystemService { // If we would like to keep a particular eye on a package, we can set the package name. private final boolean mExtraDisplayEventLogging; + private final String mExtraDisplayLoggingPackageName; private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() { @Override @@ -584,8 +585,8 @@ public final class DisplayManagerService extends SystemService { mOverlayProperties = SurfaceControl.getOverlaySupport(); mSystemReady = false; mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); - final String name = DisplayProperties.debug_vri_package().orElse(null); - mExtraDisplayEventLogging = !TextUtils.isEmpty(name); + mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null); + mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName); } public void setupSchedulerPolicies() { @@ -757,7 +758,8 @@ public final class DisplayManagerService extends SystemService { mContext.registerReceiver(mIdleModeReceiver, filter); - mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext); + mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled()) + ? SmallAreaDetectionController.create(mContext) : null; } @VisibleForTesting @@ -2934,12 +2936,17 @@ public final class DisplayManagerService extends SystemService { // Only send updates outside of DisplayManagerService for enabled displays if (display.isEnabledLocked()) { sendDisplayEventLocked(display, event); + } else if (mExtraDisplayEventLogging) { + Slog.i(TAG, "Not Sending Display Event; display is not enabled: " + display); } } private void sendDisplayEventLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) { int displayId = display.getDisplayIdLocked(); Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event); + if (mExtraDisplayEventLogging) { + Slog.i(TAG, "Deliver Display Event on Handler: " + event); + } mHandler.sendMessage(msg); } @@ -3005,6 +3012,10 @@ public final class DisplayManagerService extends SystemService { // For cached apps, save the pending event until it becomes non-cached synchronized (mPendingCallbackSelfLocked) { PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid); + if (extraLogging(callbackRecord.mPackageName)) { + Slog.i(TAG, + "Uid is cached: " + uid + ", pendingCallback: " + pendingCallback); + } if (pendingCallback == null) { mPendingCallbackSelfLocked.put(uid, new PendingCallback(callbackRecord, displayId, event)); @@ -3019,6 +3030,10 @@ public final class DisplayManagerService extends SystemService { mTempCallbacks.clear(); } + private boolean extraLogging(String packageName) { + return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName); + } + // Runs on Handler thread. // Delivers display group event notifications to callbacks. private void deliverDisplayGroupEvent(int groupId, int event) { @@ -3462,6 +3477,7 @@ public final class DisplayManagerService extends SystemService { public final int mUid; private final IDisplayManagerCallback mCallback; private @EventsMask AtomicLong mEventsMask; + private final String mPackageName; public boolean mWifiDisplayScanRequested; @@ -3471,6 +3487,9 @@ public final class DisplayManagerService extends SystemService { mUid = uid; mCallback = callback; mEventsMask = new AtomicLong(eventsMask); + + String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); + mPackageName = packageNames == null ? null : packageNames[0]; } public void updateEventsMask(@EventsMask long eventsMask) { @@ -3479,7 +3498,8 @@ public final class DisplayManagerService extends SystemService { @Override public void binderDied() { - if (DEBUG) { + if (DEBUG || mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals( + mPackageName)) { Slog.d(TAG, "Display listener for pid " + mPid + " died."); } onCallbackDied(this); @@ -3490,6 +3510,11 @@ public final class DisplayManagerService extends SystemService { */ public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) { if (!shouldSendEvent(event)) { + if (mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals( + mPackageName)) { + Slog.i(TAG, + "Not sending displayEvent: " + event + " due to mask:" + mEventsMask); + } return true; } diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java index adaa5390cb9b..bf384b02d95e 100644 --- a/services/core/java/com/android/server/display/SmallAreaDetectionController.java +++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManagerInternal; +import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.DeviceConfigInterface; import android.util.ArrayMap; @@ -30,15 +31,14 @@ 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 com.android.server.pm.pkg.PackageStateInternal; 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); + private static native void nativeUpdateSmallAreaDetection(int[] appIds, float[] thresholds); + private static native void nativeSetSmallAreaDetectionThreshold(int appId, float threshold); // TODO(b/281720315): Move this to DeviceConfig once server side ready. private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST = @@ -47,12 +47,8 @@ final class SmallAreaDetectionController { 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 = @@ -67,7 +63,6 @@ final class SmallAreaDetectionController { 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()); @@ -76,6 +71,7 @@ final class SmallAreaDetectionController { @VisibleForTesting void updateAllowlist(@Nullable String property) { + final Map<String, Float> allowPkgMap = new ArrayMap<>(); synchronized (mLock) { mAllowPkgMap.clear(); if (property != null) { @@ -86,8 +82,11 @@ final class SmallAreaDetectionController { .getStringArray(R.array.config_smallAreaDetectionAllowlist); for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString); } - updateSmallAreaDetection(); + + if (mAllowPkgMap.isEmpty()) return; + allowPkgMap.putAll(mAllowPkgMap); } + updateSmallAreaDetection(allowPkgMap); } @GuardedBy("mLock") @@ -105,43 +104,32 @@ final class SmallAreaDetectionController { } } - @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); + private void updateSmallAreaDetection(Map<String, Float> allowPkgMap) { + final SparseArray<Float> appIdThresholdList = new SparseArray(allowPkgMap.size()); + for (String pkg : allowPkgMap.keySet()) { + final float threshold = allowPkgMap.get(pkg); + final PackageStateInternal stage = mPackageManager.getPackageStateInternal(pkg); + if (stage != null) { + appIdThresholdList.put(stage.getAppId(), 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); + final int[] appIds = new int[appIdThresholdList.size()]; + final float[] thresholds = new float[appIdThresholdList.size()]; + for (int i = 0; i < appIdThresholdList.size(); i++) { + appIds[i] = appIdThresholdList.keyAt(i); + thresholds[i] = appIdThresholdList.valueAt(i); } - updateSmallAreaDetection(uids, thresholds); + updateSmallAreaDetection(appIds, thresholds); } @VisibleForTesting - void updateSmallAreaDetection(int[] uids, float[] thresholds) { - nativeUpdateSmallAreaDetection(uids, thresholds); + void updateSmallAreaDetection(int[] appIds, float[] thresholds) { + nativeUpdateSmallAreaDetection(appIds, thresholds); } - void setSmallAreaDetectionThreshold(int uid, float threshold) { - nativeSetSmallAreaDetectionThreshold(uid, threshold); + void setSmallAreaDetectionThreshold(int appId, float threshold) { + nativeSetSmallAreaDetectionThreshold(appId, threshold); } void dump(PrintWriter pw) { @@ -151,7 +139,6 @@ final class SmallAreaDetectionController { for (String pkg : mAllowPkgMap.keySet()) { pw.println(" " + pkg + " threshold = " + mAllowPkgMap.get(pkg)); } - pw.println(" mUserIds=" + Arrays.toString(mUserIds)); } } @@ -167,11 +154,15 @@ final class SmallAreaDetectionController { private final class PackageReceiver implements PackageManagerInternal.PackageListObserver { @Override public void onPackageAdded(@NonNull String packageName, int uid) { + float threshold = 0.0f; synchronized (mLock) { if (mAllowPkgMap.containsKey(packageName)) { - setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName)); + threshold = mAllowPkgMap.get(packageName); } } + if (threshold > 0.0f) { + setSmallAreaDetectionThreshold(UserHandle.getAppId(uid), threshold); + } } } } diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index fae8383bb62e..d953e8e52365 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -71,6 +71,10 @@ public class DisplayManagerFlags { Flags.FLAG_ENABLE_POWER_THROTTLING_CLAMPER, Flags::enablePowerThrottlingClamper); + private final FlagState mSmallAreaDetectionFlagState = new FlagState( + Flags.FLAG_ENABLE_SMALL_AREA_DETECTION, + Flags::enableSmallAreaDetection); + /** Returns whether connected display management is enabled or not. */ public boolean isConnectedDisplayManagementEnabled() { return mConnectedDisplayManagementFlagState.isEnabled(); @@ -147,6 +151,10 @@ public class DisplayManagerFlags { return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled(); } + public boolean isSmallAreaDetectionEnabled() { + return mSmallAreaDetectionFlagState.isEnabled(); + } + private static class FlagState { private final String mName; diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 9ab9c9def61b..9141814da6fc 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -104,3 +104,12 @@ flag { bug: "211737588" is_fixed_read_only: true } + +flag { + name: "enable_small_area_detection" + namespace: "display_manager" + description: "Feature flag for SmallAreaDetection" + bug: "298722189" + is_fixed_read_only: true +} + diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index ca23844044ca..d023913c9694 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -723,7 +723,9 @@ public class DisplayModeDirector { if (mode.getPhysicalWidth() > maxAllowedWidth || mode.getPhysicalHeight() > maxAllowedHeight || mode.getPhysicalWidth() < outSummary.minWidth - || mode.getPhysicalHeight() < outSummary.minHeight) { + || mode.getPhysicalHeight() < outSummary.minHeight + || mode.getRefreshRate() < outSummary.minPhysicalRefreshRate + || mode.getRefreshRate() > outSummary.maxPhysicalRefreshRate) { continue; } diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java new file mode 100644 index 000000000000..ff70cb35f9dc --- /dev/null +++ b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java @@ -0,0 +1,91 @@ +/* + * 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.media.projection; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Environment; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.File; + +public class MediaProjectionSessionIdGenerator { + + private static final String PREFERENCES_FILE_NAME = "media_projection_session_id"; + private static final String SESSION_ID_PREF_KEY = "media_projection_session_id_key"; + private static final int SESSION_ID_DEFAULT_VALUE = 0; + + private static final Object sInstanceLock = new Object(); + + @GuardedBy("sInstanceLock") + private static MediaProjectionSessionIdGenerator sInstance; + + private final Object mSessionIdLock = new Object(); + + @GuardedBy("mSessionIdLock") + private final SharedPreferences mSharedPreferences; + + /** Creates or returns an existing instance of {@link MediaProjectionSessionIdGenerator}. */ + public static MediaProjectionSessionIdGenerator getInstance(Context context) { + synchronized (sInstanceLock) { + if (sInstance == null) { + File preferencesFile = + new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME); + SharedPreferences preferences = + context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE); + sInstance = new MediaProjectionSessionIdGenerator(preferences); + } + return sInstance; + } + } + + @VisibleForTesting + public MediaProjectionSessionIdGenerator(SharedPreferences sharedPreferences) { + this.mSharedPreferences = sharedPreferences; + } + + /** Returns the current session ID. This value is persisted across reboots. */ + public int getCurrentSessionId() { + synchronized (mSessionIdLock) { + return getCurrentSessionIdInternal(); + } + } + + /** + * Creates and returns a new session ID. This value will be persisted as the new current session + * ID, and will be persisted across reboots. + */ + public int createAndGetNewSessionId() { + synchronized (mSessionIdLock) { + int newSessionId = getCurrentSessionId() + 1; + setSessionIdInternal(newSessionId); + return newSessionId; + } + } + + @GuardedBy("mSessionIdLock") + private void setSessionIdInternal(int value) { + mSharedPreferences.edit().putInt(SESSION_ID_PREF_KEY, value).apply(); + } + + @GuardedBy("mSessionIdLock") + private int getCurrentSessionIdInternal() { + return mSharedPreferences.getInt(SESSION_ID_PREF_KEY, SESSION_ID_DEFAULT_VALUE); + } +} diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 7db7bf538c37..30017be96085 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -505,6 +505,10 @@ public class ComputerEngine implements Computer { int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits) { if (!mUserManager.exists(userId)) return Collections.emptyList(); + + // Allow to match activities of quarantined packages. + flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS; + final String instantAppPkgName = getInstantAppPackageName(filterCallingUid); enforceCrossUserPermission(Binder.getCallingUid(), userId, false /* requireFullPermission */, false /* checkShell */, @@ -647,11 +651,6 @@ public class ComputerEngine implements Computer { flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps, false /* isImplicitImageCaptureIntentAndNotSetByDpc */); - // Only if the query is coming from the system process, - // it should be allowed to match quarantined components - if (callingUid != Process.SYSTEM_UID) { - flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; - } Intent originalIntent = null; ComponentName comp = intent.getComponent(); if (comp == null) { @@ -4047,9 +4046,6 @@ public class ComputerEngine implements Computer { flags = updateFlagsForComponent(flags, userId); enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */, false /* checkShell */, "get provider info"); - if (callingUid != Process.SYSTEM_UID) { - flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; - } ParsedProvider p = mComponentResolver.getProvider(component); if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getProviderInfo " + component + ": " + p); @@ -4679,9 +4675,6 @@ public class ComputerEngine implements Computer { int callingUid) { if (!mUserManager.exists(userId)) return null; flags = updateFlagsForComponent(flags, userId); - if (callingUid != Process.SYSTEM_UID) { - flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; - } final ProviderInfo providerInfo = mComponentResolver.queryProvider(this, name, flags, userId); boolean checkedGrants = false; @@ -4794,13 +4787,6 @@ public class ComputerEngine implements Computer { false /* checkShell */, "queryContentProviders"); if (!mUserManager.exists(userId)) return ParceledListSlice.emptyList(); flags = updateFlagsForComponent(flags, userId); - - // Only if the service query is coming from the system process, - // it should be allowed to match quarantined components - if (callingUid != Process.SYSTEM_UID) { - flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; - } - ArrayList<ProviderInfo> finalList = null; final List<ProviderInfo> matchList = mComponentResolver.queryProviders(this, processName, metaDataKey, uid, flags, userId); diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java index 148e0df7cf64..9ad8318c2b5f 100644 --- a/services/core/java/com/android/server/pm/MovePackageHelper.java +++ b/services/core/java/com/android/server/pm/MovePackageHelper.java @@ -89,6 +89,21 @@ public final class MovePackageHelper { if (packageState == null || packageState.getPkg() == null) { throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package"); } + final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState, + mPm.mUserManager.getUserIds(), true); + final UserHandle userForMove; + if (installedUserIds.length > 0) { + userForMove = UserHandle.of(installedUserIds[0]); + } else { + throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, + "Package is not installed for any user"); + } + for (int userId : installedUserIds) { + if (snapshot.shouldFilterApplicationIncludingUninstalled(packageState, callingUid, + userId)) { + throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package"); + } + } final AndroidPackage pkg = packageState.getPkg(); if (packageState.isSystem()) { throw new PackageManagerException(MOVE_FAILED_SYSTEM_PACKAGE, @@ -134,8 +149,6 @@ public final class MovePackageHelper { final String label = String.valueOf(pm.getApplicationLabel( AndroidPackageUtils.generateAppInfoWithoutState(pkg))); final int targetSdkVersion = pkg.getTargetSdkVersion(); - final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState, - mPm.mUserManager.getUserIds(), true); final String fromCodePath; if (codeFile.getParentFile().getName().startsWith( PackageManagerService.RANDOM_DIR_PREFIX)) { @@ -303,8 +316,8 @@ public final class MovePackageHelper { final PackageLite lite = ret.isSuccess() ? ret.getResult() : null; final InstallingSession installingSession = new InstallingSession(origin, move, installObserver, installFlags, /* developmentInstallFlags= */ 0, installSource, - volumeUuid, user, packageAbiOverride, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, - lite, mPm); + volumeUuid, userForMove, packageAbiOverride, + PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, lite, mPm); installingSession.movePackage(); } diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index f59188e9fd93..0fb1f7a0780c 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -196,8 +196,12 @@ public class PackageArchiver { for (int i = 0, size = mainActivities.length; i < size; ++i) { var mainActivity = mainActivities[i]; Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i); - ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( - mainActivity.title, iconPath, null); + ArchiveActivityInfo activityInfo = + new ArchiveActivityInfo( + mainActivity.title, + mainActivity.originalComponentName, + iconPath, + null); archiveActivityInfos.add(activityInfo); } @@ -215,8 +219,12 @@ public class PackageArchiver { for (int i = 0, size = mainActivities.size(); i < size; i++) { LauncherActivityInfo mainActivity = mainActivities.get(i); Path iconPath = storeIcon(packageName, mainActivity, userId, i); - ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( - mainActivity.getLabel().toString(), iconPath, null); + ArchiveActivityInfo activityInfo = + new ArchiveActivityInfo( + mainActivity.getLabel().toString(), + mainActivity.getComponentName(), + iconPath, + null); archiveActivityInfos.add(activityInfo); } @@ -593,6 +601,7 @@ public class PackageArchiver { } var archivedActivity = new ArchivedActivityParcel(); archivedActivity.title = info.getTitle(); + archivedActivity.originalComponentName = info.getOriginalComponentName(); archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap()); archivedActivity.monochromeIconBitmap = bytesFromBitmapFile( info.getMonochromeIconBitmap()); @@ -624,6 +633,7 @@ public class PackageArchiver { } var archivedActivity = new ArchivedActivityParcel(); archivedActivity.title = info.getLabel().toString(); + archivedActivity.originalComponentName = info.getComponentName(); archivedActivity.iconBitmap = info.getActivityInfo().getIconResource() == 0 ? null : bytesFromBitmap( drawableToBitmap(info.getIcon(/* density= */ 0))); diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index 651845e71924..e7499680b9a2 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -747,7 +747,7 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { @Override public void notifyComponentUsed(@NonNull String packageName, @UserIdInt int userId, - @NonNull String recentCallingPackage, @NonNull String debugInfo) { + @Nullable String recentCallingPackage, @NonNull String debugInfo) { mService.notifyComponentUsed(snapshot(), packageName, userId, recentCallingPackage, debugInfo); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index abeabc96e969..d0e2f0197df1 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4576,7 +4576,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService } void notifyComponentUsed(@NonNull Computer snapshot, @NonNull String packageName, - @UserIdInt int userId, @NonNull String recentCallingPackage, + @UserIdInt int userId, @Nullable String recentCallingPackage, @NonNull String debugInfo) { synchronized (mLock) { final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName); diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 9f4e86d527ce..3ca933a66656 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -1228,6 +1228,9 @@ public class PackageSetting extends SettingBase implements PackageStateInternal long activityInfoToken = proto.start( PackageProto.UserInfoProto.ArchiveState.ACTIVITY_INFOS); proto.write(ArchiveActivityInfo.TITLE, activityInfo.getTitle()); + proto.write( + ArchiveActivityInfo.ORIGINAL_COMPONENT_NAME, + activityInfo.getOriginalComponentName().flattenToString()); if (activityInfo.getIconBitmap() != null) { proto.write(ArchiveActivityInfo.ICON_BITMAP_PATH, activityInfo.getIconBitmap().toAbsolutePath().toString()); diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java index da14397b9c92..203e1de61f2f 100644 --- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java +++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java @@ -517,12 +517,6 @@ final class ResolveIntentHelper { if (!mUserManager.exists(userId)) return Collections.emptyList(); final int callingUid = Binder.getCallingUid(); - // Only if the service query is coming from the system process, - // it should be allowed to match quarantined components - if (callingUid != Process.SYSTEM_UID) { - flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; - } - final String instantAppPkgName = computer.getInstantAppPackageName(callingUid); flags = computer.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, false /* isImplicitImageCaptureIntentAndNotSetByDpc */); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 451b3a5256ac..e726d91c30a0 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -368,6 +368,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile private static final String ATTR_VALUE = "value"; private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time"; private static final String ATTR_ARCHIVE_ACTIVITY_TITLE = "activity-title"; + private static final String ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME = "original-component-name"; private static final String ATTR_ARCHIVE_INSTALLER_TITLE = "installer-title"; private static final String ATTR_ARCHIVE_ICON_PATH = "icon-path"; private static final String ATTR_ARCHIVE_MONOCHROME_ICON_PATH = "monochrome-icon-path"; @@ -2079,6 +2080,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile if (tagName.equals(TAG_ARCHIVE_ACTIVITY_INFO)) { String title = parser.getAttributeValue(null, ATTR_ARCHIVE_ACTIVITY_TITLE); + String originalComponentName = + parser.getAttributeValue(null, ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME); String iconAttribute = parser.getAttributeValue(null, ATTR_ARCHIVE_ICON_PATH); Path iconPath = iconAttribute == null ? null : Path.of(iconAttribute); @@ -2087,17 +2090,27 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile Path monochromeIconPath = monochromeAttribute == null ? null : Path.of( monochromeAttribute); - if (title == null || iconPath == null) { - Slog.wtf(TAG, - TextUtils.formatSimple("Missing attributes in tag %s. %s: %s, %s: %s", - TAG_ARCHIVE_ACTIVITY_INFO, ATTR_ARCHIVE_ACTIVITY_TITLE, title, + if (title == null || originalComponentName == null || iconPath == null) { + Slog.wtf( + TAG, + TextUtils.formatSimple( + "Missing attributes in tag %s. %s: %s, %s: %s, %s: %s", + TAG_ARCHIVE_ACTIVITY_INFO, + ATTR_ARCHIVE_ACTIVITY_TITLE, + title, + ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME, + originalComponentName, ATTR_ARCHIVE_ICON_PATH, iconPath)); continue; } activityInfos.add( - new ArchiveState.ArchiveActivityInfo(title, iconPath, monochromeIconPath)); + new ArchiveState.ArchiveActivityInfo( + title, + ComponentName.unflattenFromString(originalComponentName), + iconPath, + monochromeIconPath)); } } return activityInfos; @@ -2469,6 +2482,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) { serializer.startTag(null, TAG_ARCHIVE_ACTIVITY_INFO); serializer.attribute(null, ATTR_ARCHIVE_ACTIVITY_TITLE, activityInfo.getTitle()); + serializer.attribute( + null, + ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME, + activityInfo.getOriginalComponentName().flattenToString()); if (activityInfo.getIconBitmap() != null) { serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH, activityInfo.getIconBitmap().toAbsolutePath().toString()); @@ -6411,16 +6428,25 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } boolean clearPersistentPreferredActivity(IntentFilter filter, int userId) { + ArrayList<PersistentPreferredActivity> removed = null; PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId); Iterator<PersistentPreferredActivity> it = ppir.filterIterator(); boolean changed = false; while (it.hasNext()) { PersistentPreferredActivity ppa = it.next(); if (IntentFilter.filterEquals(ppa.getIntentFilter(), filter)) { + if (removed == null) { + removed = new ArrayList<>(); + } + removed.add(ppa); + } + } + if (removed != null) { + for (int i = 0; i < removed.size(); i++) { + PersistentPreferredActivity ppa = removed.get(i); ppir.removeFilter(ppa); - changed = true; - break; } + changed = true; } if (changed) { onChanged(); diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index d804e01aa31e..61e96ca3dd59 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -379,7 +379,7 @@ public class PackageInfoUtils { ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT) | flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD) | flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN); - if ((flags & PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS) != 0 + if ((flags & PackageManager.MATCH_QUARANTINED_COMPONENTS) == 0 && state.isQuarantined()) { ai.enabled = false; } else if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { diff --git a/services/core/java/com/android/server/pm/pkg/ArchiveState.java b/services/core/java/com/android/server/pm/pkg/ArchiveState.java index 4916a4a6d72a..1e40d44bd4ca 100644 --- a/services/core/java/com/android/server/pm/pkg/ArchiveState.java +++ b/services/core/java/com/android/server/pm/pkg/ArchiveState.java @@ -16,9 +16,11 @@ package com.android.server.pm.pkg; +import android.content.ComponentName; import android.annotation.NonNull; import android.annotation.Nullable; +import com.android.internal.util.AnnotationValidations; import com.android.internal.util.DataClass; import java.nio.file.Path; @@ -56,6 +58,10 @@ public class ArchiveState { @NonNull private final String mTitle; + /** The component name of the original activity (pre-archival). */ + @NonNull + private final ComponentName mOriginalComponentName; + /** * The path to the stored icon of the activity in the app's locale. Null if the app does * not define any icon (default icon would be shown on the launcher). @@ -96,11 +102,13 @@ public class ArchiveState { @DataClass.Generated.Member public ArchiveActivityInfo( @NonNull String title, + @NonNull ComponentName originalComponentName, @Nullable Path iconBitmap, @Nullable Path monochromeIconBitmap) { this.mTitle = title; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mTitle); + this.mOriginalComponentName = originalComponentName; + AnnotationValidations.validate(NonNull.class, null, mTitle); + AnnotationValidations.validate(NonNull.class, null, mOriginalComponentName); this.mIconBitmap = iconBitmap; this.mMonochromeIconBitmap = monochromeIconBitmap; @@ -116,6 +124,14 @@ public class ArchiveState { } /** + * The component name of the original activity (pre-archival). + */ + @DataClass.Generated.Member + public @NonNull ComponentName getOriginalComponentName() { + return mOriginalComponentName; + } + + /** * The path to the stored icon of the activity in the app's locale. Null if the app does * not define any icon (default icon would be shown on the launcher). */ @@ -140,6 +156,7 @@ public class ArchiveState { return "ArchiveActivityInfo { " + "title = " + mTitle + ", " + + "originalComponentName = " + mOriginalComponentName + ", " + "iconBitmap = " + mIconBitmap + ", " + "monochromeIconBitmap = " + mMonochromeIconBitmap + " }"; @@ -159,6 +176,7 @@ public class ArchiveState { //noinspection PointlessBooleanExpression return true && java.util.Objects.equals(mTitle, that.mTitle) + && java.util.Objects.equals(mOriginalComponentName, that.mOriginalComponentName) && java.util.Objects.equals(mIconBitmap, that.mIconBitmap) && java.util.Objects.equals(mMonochromeIconBitmap, that.mMonochromeIconBitmap); } @@ -171,6 +189,7 @@ public class ArchiveState { int _hash = 1; _hash = 31 * _hash + java.util.Objects.hashCode(mTitle); + _hash = 31* _hash + java.util.Objects.hashCode(mOriginalComponentName); _hash = 31 * _hash + java.util.Objects.hashCode(mIconBitmap); _hash = 31 * _hash + java.util.Objects.hashCode(mMonochromeIconBitmap); return _hash; @@ -180,7 +199,8 @@ public class ArchiveState { time = 1693590309015L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)") + inputSignatures = + "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.NonNull android.content.ComponentName mOriginalComponentName\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)") @Deprecated private void __metadata() {} @@ -224,11 +244,9 @@ public class ArchiveState { @NonNull List<ArchiveActivityInfo> activityInfos, @NonNull String installerTitle) { this.mActivityInfos = activityInfos; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mActivityInfos); + AnnotationValidations.validate(NonNull.class, null, mActivityInfos); this.mInstallerTitle = installerTitle; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mInstallerTitle); + AnnotationValidations.validate(NonNull.class, null, mInstallerTitle); // onConstructed(); // You can define this method to get a callback } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java index 7b07e5b2bb6b..cd3583b814a4 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java @@ -16,9 +16,9 @@ package com.android.server.pm.pkg; -import static android.content.pm.PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; +import static android.content.pm.PackageManager.MATCH_QUARANTINED_COMPONENTS; import android.annotation.NonNull; import android.content.pm.ComponentInfo; @@ -147,7 +147,7 @@ public class PackageUserStateUtils { return true; } - if ((flags & FILTER_OUT_QUARANTINED_COMPONENTS) != 0 && state.isQuarantined()) { + if ((flags & MATCH_QUARANTINED_COMPONENTS) == 0 && state.isQuarantined()) { return false; } diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS index 174ad3ad2e25..c33f3d9fa264 100644 --- a/services/core/java/com/android/server/stats/OWNERS +++ b/services/core/java/com/android/server/stats/OWNERS @@ -1,11 +1,10 @@ jeffreyhuang@google.com joeo@google.com -jtnguyen@google.com +monicamwang@google.com muhammadq@google.com +rayhdez@google.com rslawik@google.com -ruchirr@google.com sharaienko@google.com singhtejinder@google.com tsaichristine@google.com yaochen@google.com -yro@google.com diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 0718f2f284a5..4200fbf39ade 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -3982,7 +3982,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (wallpaper == null) { // common case, this is the first lookup post-boot of the system or // unified lock, so we bring up the saved state lazily now and recheck. - int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM; + // if we're loading the system wallpaper for the first time, also load the lock + // wallpaper to determine if the system wallpaper is system+lock or system only. + int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM | FLAG_LOCK; loadSettingsLocked(userId, false, whichLoad); wallpaper = whichSet.get(userId); if (wallpaper == null) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0b673211a1c9..de335d3d013e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -68,6 +68,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; @@ -3686,6 +3687,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { getTransitionController(), mWindowManager.mSyncEngine) : null; + if (r.getTaskFragment() != null && r.getTaskFragment().isEmbeddedWithBoundsOverride() + && transition != null) { + transition.addFlag(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); + } + final Runnable enterPipRunnable = () -> { synchronized (mGlobalLock) { if (r.getParent() == null) { diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 42376685498a..6d59b297de48 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -276,6 +276,10 @@ class BackNavigationController { // activity, we won't close the activity. backType = BackNavigationInfo.TYPE_DIALOG_CLOSE; removedWindowContainer = window; + } else if (!currentActivity.occludesParent() || currentActivity.showWallpaper()) { + // skip if current activity is translucent + backType = BackNavigationInfo.TYPE_CALLBACK; + removedWindowContainer = window; } else if (prevActivity != null) { if (!isOccluded || prevActivity.canShowWhenLocked()) { // We have another Activity in the same currentTask to go to diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 016b0ff55826..4922e9028ed9 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -489,10 +489,6 @@ class Task extends TaskFragment { private boolean mForceShowForAllUsers; - /** When set, will force the task to report as invisible. */ - static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1; - static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1; - private int mForceHiddenFlags = 0; private boolean mForceTranslucent = false; // The display category name for this task. @@ -4495,20 +4491,13 @@ class Task extends TaskFragment { * Sets/unsets the forced-hidden state flag for this task depending on {@param set}. * @return Whether the force hidden state changed */ - boolean setForceHidden(int flags, boolean set) { - int newFlags = mForceHiddenFlags; - if (set) { - newFlags |= flags; - } else { - newFlags &= ~flags; - } - if (mForceHiddenFlags == newFlags) { - return false; - } - + @Override + boolean setForceHidden(@FlagForceHidden int flags, boolean set) { final boolean wasHidden = isForceHidden(); final boolean wasVisible = isVisible(); - mForceHiddenFlags = newFlags; + if (!super.setForceHidden(flags, set)) { + return false; + } final boolean nowHidden = isForceHidden(); if (wasHidden != nowHidden) { final String reason = "setForceHidden"; @@ -4539,11 +4528,6 @@ class Task extends TaskFragment { return super.isAlwaysOnTop(); } - @Override - protected boolean isForceHidden() { - return mForceHiddenFlags != 0; - } - boolean isForceHiddenForPinnedTask() { return (mForceHiddenFlags & FLAG_FORCE_HIDDEN_FOR_PINNED_TASK) != 0; } @@ -5651,6 +5635,8 @@ class Task extends TaskFragment { if (noAnimation) { mDisplayContent.prepareAppTransition(TRANSIT_NONE); mTaskSupervisor.mNoAnimActivities.add(top); + mTransitionController.collect(top); + mTransitionController.setNoAnimation(top); ActivityOptions.abort(options); } else { updateTransitLocked(TRANSIT_TO_FRONT, options); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 7c5bc6e3bb28..906b3b55e015 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -362,6 +362,19 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ private boolean mIsolatedNav; + /** When set, will force the task to report as invisible. */ + static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1; + static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1; + static final int FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG = 1 << 2; + + @IntDef(prefix = {"FLAG_FORCE_HIDDEN_"}, value = { + FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, + FLAG_FORCE_HIDDEN_FOR_TASK_ORG, + FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG, + }, flag = true) + @interface FlagForceHidden {} + protected int mForceHiddenFlags = 0; + final Point mLastSurfaceSize = new Point(); private final Rect mTmpBounds = new Rect(); @@ -845,7 +858,25 @@ class TaskFragment extends WindowContainer<WindowContainer> { * Returns whether this TaskFragment is currently forced to be hidden for any reason. */ protected boolean isForceHidden() { - return false; + return mForceHiddenFlags != 0; + } + + /** + * Sets/unsets the forced-hidden state flag for this task depending on {@param set}. + * @return Whether the force hidden state changed + */ + boolean setForceHidden(@FlagForceHidden int flags, boolean set) { + int newFlags = mForceHiddenFlags; + if (set) { + newFlags |= flags; + } else { + newFlags &= ~flags; + } + if (mForceHiddenFlags == newFlags) { + return false; + } + mForceHiddenFlags = newFlags; + return true; } protected boolean isForceTranslucent() { diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 04164c20a372..ff766beee337 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -51,7 +51,6 @@ import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOperation; -import android.window.TaskFragmentOrganizerToken; import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; @@ -745,9 +744,9 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } - boolean isSystemOrganizer(@NonNull TaskFragmentOrganizerToken token) { + boolean isSystemOrganizer(@NonNull IBinder organizerToken) { final TaskFragmentOrganizerState state = - mTaskFragmentOrganizerState.get(token.asBinder()); + mTaskFragmentOrganizerState.get(organizerToken); return state != null && state.mIsSystemOrganizer; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 93db1caf5fdf..7d65c61193b5 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -3321,8 +3321,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mFrozen.add(wc); final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc)); changeInfo.mSnapshot = snapshotSurface; - if (isDisplayRotation) { - // This isn't cheap, so only do it for display rotations. + if (changeInfo.mRotation != wc.mDisplayContent.getRotation()) { + // This isn't cheap, so only do it for rotation change. changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma( buffer, screenshotBuffer.getColorSpace()); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9663f3aba7f1..88f72f9dbc90 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8461,16 +8461,18 @@ public class WindowManagerService extends IWindowManager.Stub return true; } // For a task session, find the activity identified by the launch cookie. - final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie( + final WindowContainerInfo wci = getTaskWindowContainerInfoForLaunchCookie( incomingSession.getTokenToRecord()); - if (wct == null) { + if (wci == null) { Slog.w(TAG, "Handling a new recording session; unable to find the " + "WindowContainerToken"); return false; } // Replace the launch cookie in the session details with the task's // WindowContainerToken. - incomingSession.setTokenToRecord(wct.asBinder()); + incomingSession.setTokenToRecord(wci.getToken().asBinder()); + // Also replace the UNKNOWN target UID with the actual UID. + incomingSession.setTargetUid(wci.getUid()); mContentRecordingController.setContentRecordingSessionLocked(incomingSession, WindowManagerService.this); return true; @@ -8798,21 +8800,41 @@ public class WindowManagerService extends IWindowManager.Stub mAtmService.setFocusedTask(task.mTaskId, touchedActivity); } + @VisibleForTesting + static class WindowContainerInfo { + private final int mUid; + @NonNull private final WindowContainerToken mToken; + + private WindowContainerInfo(int uid, @NonNull WindowContainerToken token) { + this.mUid = uid; + this.mToken = token; + } + + public int getUid() { + return mUid; + } + + @NonNull + public WindowContainerToken getToken() { + return mToken; + } + } + /** - * Retrieve the {@link WindowContainerToken} of the task that contains the activity started - * with the given launch cookie. + * Retrieve the {@link WindowContainerInfo} of the task that contains the activity started with + * the given launch cookie. * * @param launchCookie the launch cookie set on the {@link ActivityOptions} when starting an - * activity + * activity * @return a token representing the task containing the activity started with the given launch - * cookie, or {@code null} if the token couldn't be found. + * cookie, or {@code null} if the token couldn't be found. */ @VisibleForTesting @Nullable - WindowContainerToken getTaskWindowContainerTokenForLaunchCookie(@NonNull IBinder launchCookie) { + WindowContainerInfo getTaskWindowContainerInfoForLaunchCookie(@NonNull IBinder launchCookie) { // Find the activity identified by the launch cookie. - final ActivityRecord targetActivity = mRoot.getActivity( - activity -> activity.mLaunchCookie == launchCookie); + final ActivityRecord targetActivity = + mRoot.getActivity(activity -> activity.mLaunchCookie == launchCookie); if (targetActivity == null) { Slog.w(TAG, "Unable to find the activity for this launch cookie"); return null; @@ -8827,7 +8849,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Unable to find the WindowContainerToken for " + targetActivity.getName()); return null; } - return taskWindowContainerToken; + return new WindowContainerInfo(targetActivity.getUid(), taskWindowContainerToken); } /** diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index dd9a88f72bde..5ed6caffe1fb 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -24,7 +24,9 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; @@ -34,6 +36,8 @@ import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATI import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS; import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN; +import static android.window.WindowContainerTransaction.Change.CHANGE_FOCUSABLE; +import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN; import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION; @@ -61,6 +65,7 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; +import static com.android.server.wm.TaskFragment.FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -821,6 +826,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return TRANSACT_EFFECTS_NONE; } + int effects = TRANSACT_EFFECTS_NONE; // When the TaskFragment is resized, we may want to create a change transition for it, for // which we want to defer the surface update until we determine whether or not to start // change transition. @@ -843,7 +849,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub c.getConfiguration().windowConfiguration.setBounds(absBounds); taskFragment.setRelativeEmbeddedBounds(relBounds); } - final int effects = applyChanges(taskFragment, c); + if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) { + if (taskFragment.setForceHidden( + FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG, c.getHidden())) { + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + } + effects |= applyChanges(taskFragment, c); + if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) { taskFragment.initializeChangeTransition(mTmpBounds0); } @@ -1393,6 +1406,24 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub taskFragment.setIsolatedNav(isolatedNav); break; } + case OP_TYPE_REORDER_TO_BOTTOM_OF_TASK: { + final Task task = taskFragment.getTask(); + if (task != null) { + task.mChildren.remove(taskFragment); + task.mChildren.add(0, taskFragment); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + break; + } + case OP_TYPE_REORDER_TO_TOP_OF_TASK: { + final Task task = taskFragment.getTask(); + if (task != null) { + task.mChildren.remove(taskFragment); + task.mChildren.add(taskFragment); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + break; + } } return effects; } @@ -1420,6 +1451,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return false; } + if ((opType == OP_TYPE_REORDER_TO_BOTTOM_OF_TASK + || opType == OP_TYPE_REORDER_TO_TOP_OF_TASK) + && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { + final Throwable exception = new SecurityException( + "Only a system organizer can perform OP_TYPE_REORDER_TO_BOTTOM_OF_TASK or " + + "OP_TYPE_REORDER_TO_TOP_OF_TASK." + ); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment, + opType, exception); + return false; + } + final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken(); return secondaryFragmentToken == null || validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType, @@ -1920,6 +1963,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub * For config change on {@link TaskFragment}, we only support the following operations: * {@link WindowContainerTransaction#setRelativeBounds(WindowContainerToken, Rect)}, * {@link WindowContainerTransaction#setWindowingMode(WindowContainerToken, int)}. + * + * For a system organizer, we additionally support + * {@link WindowContainerTransaction#setHidden(WindowContainerToken, boolean)}, and + * {@link WindowContainerTransaction#setFocusable(WindowContainerToken, boolean)}. See + * {@link TaskFragmentOrganizerController#registerOrganizer(ITaskFragmentOrganizer, boolean)} */ private void enforceTaskFragmentConfigChangeAllowed(@NonNull String func, @Nullable WindowContainer wc, @NonNull WindowContainerTransaction.Change change, @@ -1938,31 +1986,49 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub throw new SecurityException(msg); } - final int changeMask = change.getChangeMask(); - final int configSetMask = change.getConfigSetMask(); - final int windowSetMask = change.getWindowSetMask(); - if (changeMask == 0 && configSetMask == 0 && windowSetMask == 0 - && change.getWindowingMode() >= 0) { - // The change contains only setWindowingMode, which is allowed. - return; + final int originalChangeMask = change.getChangeMask(); + final int originalConfigSetMask = change.getConfigSetMask(); + final int originalWindowSetMask = change.getWindowSetMask(); + + int changeMaskToBeChecked = originalChangeMask; + int configSetMaskToBeChecked = originalConfigSetMask; + int windowSetMaskToBeChecked = originalWindowSetMask; + + if (mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { + // System organizer is allowed to update the hidden and focusable state. + // We unset the CHANGE_HIDDEN and CHANGE_FOCUSABLE bits because they are checked here. + changeMaskToBeChecked &= ~CHANGE_HIDDEN; + changeMaskToBeChecked &= ~CHANGE_FOCUSABLE; } - if (changeMask != CHANGE_RELATIVE_BOUNDS - || configSetMask != ActivityInfo.CONFIG_WINDOW_CONFIGURATION - || windowSetMask != WindowConfiguration.WINDOW_CONFIG_BOUNDS) { - // None of the change should be requested from a TaskFragment organizer except - // setRelativeBounds and setWindowingMode. - // For setRelativeBounds, we don't need to check whether it is outside of the Task + + // setRelativeBounds is allowed. + if ((changeMaskToBeChecked & CHANGE_RELATIVE_BOUNDS) != 0 + && (configSetMaskToBeChecked & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0 + && (windowSetMaskToBeChecked & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) { + // For setRelativeBounds, we don't need to check whether it is outside the Task // bounds, because it is possible that the Task is also resizing, for which we don't // want to throw an exception. The bounds will be adjusted in // TaskFragment#translateRelativeBoundsToAbsoluteBounds. - String msg = "Permission Denial: " + func + " from pid=" - + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() - + " trying to apply changes of changeMask=" + changeMask - + " configSetMask=" + configSetMask + " windowSetMask=" + windowSetMask - + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer; - Slog.w(TAG, msg); - throw new SecurityException(msg); + changeMaskToBeChecked &= ~CHANGE_RELATIVE_BOUNDS; + configSetMaskToBeChecked &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION; + windowSetMaskToBeChecked &= ~WindowConfiguration.WINDOW_CONFIG_BOUNDS; } + + if (changeMaskToBeChecked == 0 && configSetMaskToBeChecked == 0 + && windowSetMaskToBeChecked == 0) { + // All the changes have been checked. + // Note that setWindowingMode is always allowed, so we don't need to check the mask. + return; + } + + final String msg = "Permission Denial: " + func + " from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " trying to apply changes of changeMask=" + originalChangeMask + + " configSetMask=" + originalConfigSetMask + + " windowSetMask=" + originalWindowSetMask + + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer; + Slog.w(TAG, msg); + throw new SecurityException(msg); } private void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams, @@ -2019,7 +2085,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer(); taskFragment.setTaskFragmentOrganizer(organizerToken, ownerActivity.getUid(), ownerActivity.info.processName, - mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken)); + mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder())); final int position; if (creationParams.getPairedPrimaryFragmentToken() != null) { // When there is a paired primary TaskFragment, we want to place the new TaskFragment diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index e434f296bbb5..7d21dbf85a66 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -569,6 +569,7 @@ class WindowToken extends WindowContainer<WindowState> { && asActivityRecord() != null && isVisible()) { // Trigger an activity level rotation transition. mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_CHANGE, this); + mTransitionController.collectVisibleChange(this); mTransitionController.setReady(this); } final int originalRotation = getWindowConfiguration().getRotation(); diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp index b256f168f2af..1844d3063cd8 100644 --- a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp +++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp @@ -24,33 +24,33 @@ #include "utils/Log.h" namespace android { -static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids, +static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray jappIds, jfloatArray jthresholds) { - if (juids == nullptr || jthresholds == nullptr) return; + if (jappIds == nullptr || jthresholds == nullptr) return; - ScopedIntArrayRO uids(env, juids); + ScopedIntArrayRO appIds(env, jappIds); ScopedFloatArrayRO thresholds(env, jthresholds); - if (uids.size() != thresholds.size()) { - ALOGE("uids size exceeds thresholds size!"); + if (appIds.size() != thresholds.size()) { + ALOGE("appIds size exceeds thresholds size!"); return; } - std::vector<int32_t> uidVector; + std::vector<int32_t> appIdVector; std::vector<float> thresholdVector; - size_t size = uids.size(); - uidVector.reserve(size); + size_t size = appIds.size(); + appIdVector.reserve(size); thresholdVector.reserve(size); for (int i = 0; i < size; i++) { - uidVector.push_back(static_cast<int32_t>(uids[i])); + appIdVector.push_back(static_cast<int32_t>(appIds[i])); thresholdVector.push_back(static_cast<float>(thresholds[i])); } - SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector); + SurfaceComposerClient::updateSmallAreaDetection(appIdVector, thresholdVector); } -static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid, +static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint appId, jfloat threshold) { - SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold); + SurfaceComposerClient::setSmallAreaDetectionThreshold(appId, threshold); } static const JNINativeMethod gMethods[] = { diff --git a/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp index fc5a11360c8c..1e3cfd049e65 100644 --- a/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp +++ b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp @@ -15,106 +15,99 @@ */ #include <android_runtime/AndroidRuntime.h> -#include <nativehelper/JNIHelp.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> #include <jni.h> +#include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> - -#include <utils/misc.h> +#include <string.h> #include <sys/ioctl.h> #include <sys/mount.h> #include <utils/Log.h> +#include <utils/misc.h> +namespace android { -#include <inttypes.h> -#include <fcntl.h> -#include <errno.h> -#include <string.h> +uint64_t get_block_device_size(int fd) { + uint64_t size = 0; + int ret; -namespace android { + ret = ioctl(fd, BLKGETSIZE64, &size); - uint64_t get_block_device_size(int fd) - { - uint64_t size = 0; - int ret; + if (ret) return 0; - ret = ioctl(fd, BLKGETSIZE64, &size); + return size; +} - if (ret) - return 0; +int wipe_block_device(int fd) { + uint64_t range[2]; + int ret; + uint64_t len = get_block_device_size(fd); - return size; - } + range[0] = 0; + range[1] = len; - int wipe_block_device(int fd) - { - uint64_t range[2]; - int ret; - uint64_t len = get_block_device_size(fd); + if (range[1] == 0) return 0; + ret = ioctl(fd, BLKSECDISCARD, &range); + if (ret < 0) { + ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno)); range[0] = 0; range[1] = len; - - if (range[1] == 0) - return 0; - - ret = ioctl(fd, BLKSECDISCARD, &range); + ret = ioctl(fd, BLKDISCARD, &range); if (ret < 0) { - ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno)); - range[0] = 0; - range[1] = len; - ret = ioctl(fd, BLKDISCARD, &range); - if (ret < 0) { - ALOGE("Discard failed: %s\n", strerror(errno)); - return -1; - } else { - ALOGE("Wipe via secure discard failed, used non-secure discard instead\n"); - return 0; - } - + ALOGE("Discard failed: %s\n", strerror(errno)); + return -1; + } else { + ALOGE("Wipe via secure discard failed, used non-secure discard instead\n"); + return 0; } - - return ret; } - static jlong com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath) - { - ScopedUtfChars path(env, jpath); - int fd = open(path.c_str(), O_RDONLY); + return ret; +} - if (fd < 0) - return 0; +static jlong com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, + jclass, + jstring jpath) { + ScopedUtfChars path(env, jpath); + int fd = open(path.c_str(), O_RDONLY); - const uint64_t size = get_block_device_size(fd); + if (fd < 0) return 0; - close(fd); + const uint64_t size = get_block_device_size(fd); - return size; - } + close(fd); - static int com_android_server_pdb_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) { - ScopedUtfChars path(env, jpath); - int fd = open(path.c_str(), O_WRONLY); + return size; +} - if (fd < 0) - return 0; +static int com_android_server_pdb_PersistentDataBlockService_wipe(JNIEnv *env, jclass, + jstring jpath) { + ScopedUtfChars path(env, jpath); + int fd = open(path.c_str(), O_WRONLY); - const int ret = wipe_block_device(fd); + if (fd < 0) return 0; - close(fd); + const int ret = wipe_block_device(fd); - return ret; - } + close(fd); - static const JNINativeMethod sMethods[] = { - /* name, signature, funcPtr */ - {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize}, - {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_pdb_PersistentDataBlockService_wipe}, - }; + return ret; +} - int register_android_server_pdb_PersistentDataBlockService(JNIEnv* env) - { - return jniRegisterNativeMethods(env, "com/android/server/pdb/PersistentDataBlockService", - sMethods, NELEM(sMethods)); - } +static const JNINativeMethod sMethods[] = { + /* name, signature, funcPtr */ + {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", + (void *)com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize}, + {"nativeWipe", "(Ljava/lang/String;)I", + (void *)com_android_server_pdb_PersistentDataBlockService_wipe}, +}; + +int register_android_server_pdb_PersistentDataBlockService(JNIEnv *env) { + return jniRegisterNativeMethods(env, "com/android/server/pdb/PersistentDataBlockService", + sMethods, NELEM(sMethods)); +} } /* namespace android */
\ No newline at end of file diff --git a/services/midi/Android.bp b/services/midi/Android.bp index 5adcfbaf432e..4b5f8a7bf0ac 100644 --- a/services/midi/Android.bp +++ b/services/midi/Android.bp @@ -19,4 +19,7 @@ java_library_static { defaults: ["platform_service_defaults"], srcs: [":services.midi-sources"], libs: ["services.core"], + static_libs: [ + "aconfig_midi_flags_java_lib", + ], } diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index a8902fcf77af..2f47cc7160b7 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -16,6 +16,8 @@ package com.android.server.midi; +import static com.android.media.midi.flags.Flags.virtualUmp; + import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; @@ -1549,6 +1551,12 @@ public class MidiService extends IMidiManager.Stub { return; } + if (!virtualUmp()) { + Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName + + ": virtual UMP flag not enabled"); + return; + } + Bundle properties = null; int numPorts = 0; boolean isPrivate = false; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index 952cfc48b583..cbedcaf97358 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -43,6 +43,7 @@ import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.PropertyInvalidatedCache; +import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.SuspendDialogInfo; @@ -873,12 +874,20 @@ public class PackageManagerSettingsTests { .setUid(packageSetting.getAppId()) .hideAsFinal()); - ArchiveState archiveState = new ArchiveState( - List.of(new ArchiveState.ArchiveActivityInfo("title1", Path.of("/path1"), - Path.of("/monochromePath1")), - new ArchiveState.ArchiveActivityInfo("title2", Path.of("/path2"), - Path.of("/monochromePath2"))), - "installerTitle"); + ArchiveState archiveState = + new ArchiveState( + List.of( + new ArchiveState.ArchiveActivityInfo( + "title1", + new ComponentName("pkg1", "class1"), + Path.of("/path1"), + Path.of("/monochromePath1")), + new ArchiveState.ArchiveActivityInfo( + "title2", + new ComponentName("pkg2", "class2"), + Path.of("/path2"), + Path.of("/monochromePath2"))), + "installerTitle"); packageSetting.modifyUserState(UserHandle.SYSTEM.getIdentifier()).setArchiveState( archiveState); settings.mPackages.put(PACKAGE_NAME_1, packageSetting); diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java index 58ae7406580e..87a297b0e86f 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.SuspendDialogInfo; import android.content.pm.overlay.OverlayPaths; @@ -192,8 +193,8 @@ public class PackageUserStateTest { return new SuspendParams(dialogInfo, appExtras, launcherExtras); } - private static PersistableBundle createPersistableBundle(String lKey, long lValue, String sKey, - String sValue, String dKey, double dValue) { + private static PersistableBundle createPersistableBundle( + String lKey, long lValue, String sKey, String sValue, String dKey, double dValue) { final PersistableBundle result = new PersistableBundle(3); if (lKey != null) { result.putLong("com.unit_test." + lKey, lValue); @@ -320,6 +321,7 @@ public class PackageUserStateTest { assertEquals(0L, state.getLastPackageUsageTimeInMills()[i]); } } + private static void assertLastPackageUsageSet( PackageStateUnserialized state, int reason, long value) throws Exception { for (int i = state.getLastPackageUsageTimeInMills().length - 1; i >= 0; --i) { @@ -330,6 +332,7 @@ public class PackageUserStateTest { } } } + @Test public void testPackageUseReasons() throws Exception { PackageSetting packageSetting = Mockito.mock(PackageSetting.class); @@ -377,6 +380,7 @@ public class PackageUserStateTest { assertTrue(testState.setOverlayPaths(new OverlayPaths.Builder().build())); assertFalse(testState.setOverlayPaths(null)); } + @Test public void testSharedLibOverlayPaths() { final PackageUserStateImpl testState = new PackageUserStateImpl(); @@ -401,8 +405,12 @@ public class PackageUserStateTest { @Test public void archiveState() { PackageUserStateImpl packageUserState = new PackageUserStateImpl(); - ArchiveState.ArchiveActivityInfo archiveActivityInfo = new ArchiveState.ArchiveActivityInfo( - "appTitle", Path.of("/path1"), Path.of("/path2")); + ArchiveState.ArchiveActivityInfo archiveActivityInfo = + new ArchiveState.ArchiveActivityInfo( + "appTitle", + new ComponentName("pkg", "class"), + Path.of("/path1"), + Path.of("/path2")); ArchiveState archiveState = new ArchiveState(List.of(archiveActivityInfo), "installerTitle"); packageUserState.setArchiveState(archiveState); 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 d021f1d5aaea..16d72e40fbb5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -117,7 +117,6 @@ import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.notifications.DisplayNotificationManager; 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; @@ -312,7 +311,6 @@ public class DisplayManagerServiceTest { @Mock SensorManager mSensorManager; @Mock DisplayDeviceConfig mMockDisplayDeviceConfig; @Mock PackageManagerInternal mMockPackageManagerInternal; - @Mock UserManagerInternal mMockUserManagerInternal; @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor; @@ -336,8 +334,6 @@ public class DisplayManagerServiceTest { 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/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index c4f72b307eb7..6a95d5c57024 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -102,6 +102,9 @@ import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.testutils.FakeDeviceConfigInterface; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -121,26 +124,28 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import junitparams.JUnitParamsRunner; -import junitparams.Parameters; - @SmallTest @RunWith(JUnitParamsRunner.class) public class DisplayModeDirectorTest { public static Collection<Object[]> getAppRequestedSizeTestCases() { var appRequestedSizeTestCases = Arrays.asList(new Object[][] { - {DEFAULT_MODE_75.getModeId(), Float.POSITIVE_INFINITY, - DEFAULT_MODE_75.getRefreshRate(), Map.of()}, - {APP_MODE_HIGH_90.getModeId(), Float.POSITIVE_INFINITY, - APP_MODE_HIGH_90.getRefreshRate(), - Map.of( + {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(), + /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY, + /*expectedAppRequestedRefreshRate*/ DEFAULT_MODE_75.getRefreshRate(), + /*votesWithPriorities*/ Map.of()}, + {/*expectedBaseModeId*/ APP_MODE_HIGH_90.getModeId(), + /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY, + /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(), + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), APP_MODE_HIGH_90.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()))}, - {LIMIT_MODE_70.getModeId(), Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, - Map.of( + {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), + /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY, + /*expectedAppRequestedRefreshRate*/ Float.POSITIVE_INFINITY, + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), APP_MODE_HIGH_90.getPhysicalHeight()), @@ -149,9 +154,10 @@ public class DisplayModeDirectorTest { Vote.PRIORITY_LOW_POWER_MODE, Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight()))}, - {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(), - LIMIT_MODE_70.getRefreshRate(), - Map.of( + {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), + /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_65.getPhysicalWidth(), APP_MODE_65.getPhysicalHeight()), @@ -160,9 +166,10 @@ public class DisplayModeDirectorTest { Vote.PRIORITY_LOW_POWER_MODE, Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight()))}, - {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(), - LIMIT_MODE_70.getRefreshRate(), - Map.of( + {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), + /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_65.getPhysicalWidth(), APP_MODE_65.getPhysicalHeight()), @@ -173,10 +180,12 @@ public class DisplayModeDirectorTest { 0, 0, LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight(), - 0, Float.POSITIVE_INFINITY)), false}, - {APP_MODE_65.getModeId(), APP_MODE_65.getRefreshRate(), - APP_MODE_65.getRefreshRate(), - Map.of( + 0, Float.POSITIVE_INFINITY)), + /*displayResolutionRangeVotingEnabled*/ false}, + {/*expectedBaseModeId*/ APP_MODE_65.getModeId(), + /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*votesWithPriorities*/ Map.of( Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_65.getPhysicalWidth(), APP_MODE_65.getPhysicalHeight()), @@ -187,7 +196,40 @@ public class DisplayModeDirectorTest { 0, 0, LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight(), - 0, Float.POSITIVE_INFINITY)), true}}); + 0, Float.POSITIVE_INFINITY)), + /*displayResolutionRangeVotingEnabled*/ true}, + {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(), + /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(), + /*votesWithPriorities*/ Map.of( + Vote.PRIORITY_APP_REQUEST_SIZE, + Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), + APP_MODE_HIGH_90.getPhysicalHeight()), + Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), + Vote.PRIORITY_LOW_POWER_MODE, + Vote.forSizeAndPhysicalRefreshRatesRange( + 0, 0, + LIMIT_MODE_70.getPhysicalWidth(), + LIMIT_MODE_70.getPhysicalHeight(), + 0, APP_MODE_65.getRefreshRate())), + /*displayResolutionRangeVotingEnabled*/ false}, + {/*expectedBaseModeId*/ DEFAULT_MODE_60.getModeId(), // Resolution == APP_MODE_65 + /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(), + /*votesWithPriorities*/ Map.of( + Vote.PRIORITY_APP_REQUEST_SIZE, + Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), + APP_MODE_HIGH_90.getPhysicalHeight()), + Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), + Vote.PRIORITY_LOW_POWER_MODE, + Vote.forSizeAndPhysicalRefreshRatesRange( + 0, 0, + LIMIT_MODE_70.getPhysicalWidth(), + LIMIT_MODE_70.getPhysicalHeight(), + 0, APP_MODE_65.getRefreshRate())), + /*displayResolutionRangeVotingEnabled*/ true}}); final var res = new ArrayList<Object[]>(appRequestedSizeTestCases.size() * 2); @@ -218,6 +260,8 @@ public class DisplayModeDirectorTest { private static final boolean DEBUG = false; private static final float FLOAT_TOLERANCE = 0.01f; + private static final Display.Mode DEFAULT_MODE_60 = new Display.Mode( + /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60); private static final Display.Mode APP_MODE_65 = new Display.Mode( /*modeId=*/65, /*width=*/1900, /*height=*/1900, 65); private static final Display.Mode LIMIT_MODE_70 = new Display.Mode( @@ -227,8 +271,7 @@ public class DisplayModeDirectorTest { private static final Display.Mode APP_MODE_HIGH_90 = new Display.Mode( /*modeId=*/90, /*width=*/3000, /*height=*/3000, 90); private static final Display.Mode[] TEST_MODES = new Display.Mode[] { - new Display.Mode( - /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60), + DEFAULT_MODE_60, APP_MODE_65, LIMIT_MODE_70, DEFAULT_MODE_75, diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 410ae35aa790..367e14b37180 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -228,7 +228,7 @@ public class BroadcastQueueTest { LocalServices.removeServiceForTest(AlarmManagerInternal.class); LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt); doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); - doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt()); + doNothing().when(mPackageManagerInt).notifyComponentUsed(any(), anyInt(), any(), any()); doAnswer((invocation) -> { return getUidForPackage(invocation.getArgument(0)); }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM)); @@ -1014,8 +1014,9 @@ public class BroadcastQueueTest { eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER)); // Confirm that we unstopped manifest receivers - verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState( - eq(receiverApp.info.packageName), eq(false), eq(UserHandle.USER_SYSTEM)); + verify(mAms.mPackageManagerInt, atLeastOnce()).notifyComponentUsed( + eq(receiverApp.info.packageName), eq(UserHandle.USER_SYSTEM), + eq(callerApp.info.packageName), any()); } // Confirm that we've reported expected usage events diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java index 1ce79a5b596b..05ac5b5720e6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java @@ -16,8 +16,6 @@ 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; @@ -35,7 +33,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.LocalServices; -import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.pkg.PackageStateInternal; import org.junit.Before; import org.junit.Rule; @@ -55,7 +53,10 @@ public class SmallAreaDetectionControllerTest { @Mock private PackageManagerInternal mMockPackageManagerInternal; @Mock - private UserManagerInternal mMockUserManagerInternal; + private PackageStateInternal mMockPkgStateA; + @Mock + private PackageStateInternal mMockPkgStateB; + private SmallAreaDetectionController mSmallAreaDetectionController; @@ -64,29 +65,18 @@ public class SmallAreaDetectionControllerTest { 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; + private static final int APP_ID_A = 11111; + private static final int APP_ID_B = 22222; @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); + + when(mMockPackageManagerInternal.getPackageStateInternal(PKG_A)).thenReturn(mMockPkgStateA); + when(mMockPackageManagerInternal.getPackageStateInternal(PKG_B)).thenReturn(mMockPkgStateB); + when(mMockPkgStateA.getAppId()).thenReturn(APP_ID_A); + when(mMockPkgStateB.getAppId()).thenReturn(APP_ID_B); mSmallAreaDetectionController = spy(new SmallAreaDetectionController( new ContextWrapper(ApplicationProvider.getApplicationContext()), @@ -99,9 +89,9 @@ public class SmallAreaDetectionControllerTest { 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), + final int[] resultAppIdArray = {APP_ID_A, APP_ID_B}; + final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray), eq(resultThresholdArray)); } @@ -110,9 +100,9 @@ public class SmallAreaDetectionControllerTest { 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), + final int[] resultAppIdArray = {APP_ID_B}; + final float[] resultThresholdArray = {THRESHOLD_B}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray), eq(resultThresholdArray)); } @@ -122,9 +112,9 @@ public class SmallAreaDetectionControllerTest { 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), + final int[] resultAppIdArray = {APP_ID_A}; + final float[] resultThresholdArray = {THRESHOLD_A}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray), eq(resultThresholdArray)); } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index eb50556821eb..610ea903767e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.AppOpsManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; @@ -427,6 +428,7 @@ public class PackageArchiverTest { for (LauncherActivityInfo mainActivity : createLauncherActivities()) { ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo( mainActivity.getLabel().toString(), + mainActivity.getComponentName(), ICON_PATH, null); activityInfos.add(activityInfo); } @@ -437,9 +439,11 @@ public class PackageArchiverTest { ActivityInfo activityInfo = mock(ActivityInfo.class); LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class); when(activity1.getLabel()).thenReturn("activity1"); + when(activity1.getComponentName()).thenReturn(new ComponentName("pkg1", "class1")); when(activity1.getActivityInfo()).thenReturn(activityInfo); LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class); when(activity2.getLabel()).thenReturn("activity2"); + when(activity2.getComponentName()).thenReturn(new ComponentName("pkg2", "class2")); when(activity2.getActivityInfo()).thenReturn(activityInfo); return List.of(activity1, activity2); } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index e8cbcf9a6874..a3d415e4918f 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -337,11 +337,7 @@ public class VirtualDeviceManagerServiceTest { LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); - mSetFlagsRule.disableFlags(Flags.FLAG_VDM_PUBLIC_APIS); - mSetFlagsRule.disableFlags(Flags.FLAG_DYNAMIC_POLICY); - mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS); - mSetFlagsRule.disableFlags(Flags.FLAG_VDM_CUSTOM_HOME); - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_NATIVE_VDM); + mSetFlagsRule.initAllFlagsToReleaseConfigDefault(); doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt()); diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java index 12d6161eb718..5cc84b197e03 100644 --- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java @@ -59,6 +59,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.Collections; import java.util.List; /** @@ -103,6 +104,10 @@ public class ContentCaptureManagerServiceTest { private boolean mDevCfgEnableContentProtectionReceiver; + private List<List<String>> mDevCfgContentProtectionRequiredGroups = List.of(List.of("a")); + + private List<List<String>> mDevCfgContentProtectionOptionalGroups = Collections.emptyList(); + private int mContentProtectionBlocklistManagersCreated; private int mContentProtectionServiceInfosCreated; @@ -374,7 +379,21 @@ public class ContentCaptureManagerServiceTest { } @Test - public void isContentProtectionReceiverEnabled_withoutManagers() { + public void isContentProtectionReceiverEnabled_true() { + when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true); + when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true); + mDevCfgEnableContentProtectionReceiver = true; + mContentCaptureManagerService = new TestContentCaptureManagerService(); + + boolean actual = + mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted( + USER_ID, PACKAGE_NAME); + + assertThat(actual).isTrue(); + } + + @Test + public void isContentProtectionReceiverEnabled_false_withoutManagers() { boolean actual = mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted( USER_ID, PACKAGE_NAME); @@ -385,7 +404,7 @@ public class ContentCaptureManagerServiceTest { } @Test - public void isContentProtectionReceiverEnabled_disabledWithFlag() { + public void isContentProtectionReceiverEnabled_false_disabledWithFlag() { mDevCfgEnableContentProtectionReceiver = true; mContentCaptureManagerService = new TestContentCaptureManagerService(); mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false; @@ -400,6 +419,22 @@ public class ContentCaptureManagerServiceTest { } @Test + public void isContentProtectionReceiverEnabled_false_emptyGroups() { + mDevCfgEnableContentProtectionReceiver = true; + mDevCfgContentProtectionRequiredGroups = Collections.emptyList(); + mDevCfgContentProtectionOptionalGroups = Collections.emptyList(); + mContentCaptureManagerService = new TestContentCaptureManagerService(); + + boolean actual = + mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted( + USER_ID, PACKAGE_NAME); + + assertThat(actual).isFalse(); + verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt()); + verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString()); + } + + @Test public void onLoginDetected_disabledAfterConstructor() { mDevCfgEnableContentProtectionReceiver = true; mContentCaptureManagerService = new TestContentCaptureManagerService(); @@ -525,6 +560,10 @@ public class ContentCaptureManagerServiceTest { super(sContext); this.mDevCfgEnableContentProtectionReceiver = ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver; + this.mDevCfgContentProtectionRequiredGroups = + ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionRequiredGroups; + this.mDevCfgContentProtectionOptionalGroups = + ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionOptionalGroups; } @Override diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java new file mode 100644 index 000000000000..07cdf4df47ae --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java @@ -0,0 +1,103 @@ +/* + * 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.media.projection; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.content.SharedPreferences; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; + +/** + * Tests for the {@link MediaProjectionSessionIdGenerator} class. + * + * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionSessionIdGeneratorTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class MediaProjectionSessionIdGeneratorTest { + + private static final String TEST_PREFS_FILE = "media-projection-session-id-test"; + + private final Context mContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); + private final File mSharedPreferencesFile = new File(mContext.getCacheDir(), TEST_PREFS_FILE); + private final SharedPreferences mSharedPreferences = createSharePreferences(); + private final MediaProjectionSessionIdGenerator mGenerator = + createGenerator(mSharedPreferences); + + @Before + public void setUp() { + mSharedPreferences.edit().clear().commit(); + } + + @After + public void tearDown() { + mSharedPreferences.edit().clear().commit(); + mSharedPreferencesFile.delete(); + } + + @Test + public void getCurrentSessionId_byDefault_returns0() { + assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); + } + + @Test + public void getCurrentSessionId_multipleTimes_returnsSameValue() { + assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); + assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); + assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); + } + + @Test + public void createAndGetNewSessionId_returnsIncrementedId() { + int previousValue = mGenerator.getCurrentSessionId(); + + int newValue = mGenerator.createAndGetNewSessionId(); + + assertThat(newValue).isEqualTo(previousValue + 1); + } + + @Test + public void createAndGetNewSessionId_persistsNewValue() { + int newValue = mGenerator.createAndGetNewSessionId(); + + MediaProjectionSessionIdGenerator newInstance = createGenerator(createSharePreferences()); + + assertThat(newInstance.getCurrentSessionId()).isEqualTo(newValue); + } + + private SharedPreferences createSharePreferences() { + return mContext.getSharedPreferences(mSharedPreferencesFile, Context.MODE_PRIVATE); + } + + private MediaProjectionSessionIdGenerator createGenerator(SharedPreferences sharedPreferences) { + return new MediaProjectionSessionIdGenerator(sharedPreferences); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 8e7ba7030e82..dd7dec0bdb2b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -130,12 +130,22 @@ public class BackNavigationControllerTests extends WindowTestsBase { // verify if back animation would start. assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); - // reset drawing status + // reset drawing status to test translucent activity backNavigationInfo.onBackNavigationFinished(false); mBackNavigationController.clearBackAnimations(); - topTask.forAllWindows(w -> { - makeWindowVisibleAndDrawn(w); - }, true); + final ActivityRecord topActivity = topTask.getTopMostActivity(); + makeWindowVisibleAndDrawn(topActivity.findMainWindow()); + // simulate translucent + topActivity.setOccludesParent(false); + backNavigationInfo = startBackNavigation(); + assertThat(typeToString(backNavigationInfo.getType())) + .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); + + // reset drawing status to test keyguard occludes + topActivity.setOccludesParent(true); + backNavigationInfo.onBackNavigationFinished(false); + mBackNavigationController.clearBackAnimations(); + makeWindowVisibleAndDrawn(topActivity.findMainWindow()); setupKeyguardOccluded(); backNavigationInfo = startBackNavigation(); assertThat(typeToString(backNavigationInfo.getType())) @@ -201,9 +211,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { // reset drawing status backNavigationInfo.onBackNavigationFinished(false); mBackNavigationController.clearBackAnimations(); - testCase.recordFront.forAllWindows(w -> { - makeWindowVisibleAndDrawn(w); - }, true); + makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow()); setupKeyguardOccluded(); backNavigationInfo = startBackNavigation(); assertThat(typeToString(backNavigationInfo.getType())) diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 2bf13857e537..6235b3b67145 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -26,7 +26,9 @@ import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; @@ -1655,6 +1657,127 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertEquals(frontMostTaskFragment, tf0); } + @Test + public void testApplyTransaction_reorderToBottomOfTask() { + mController.unregisterOrganizer(mIOrganizer); + mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); + final Task task = createTask(mDisplayContent); + // Create a non-embedded Activity at the bottom. + final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + final TaskFragment tf0 = createTaskFragment(task); + final TaskFragment tf1 = createTaskFragment(task); + // Create a non-embedded Activity at the top. + final ActivityRecord topActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + + // Ensure correct order of the children before the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + + // Reorder TaskFragment to bottom + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_REORDER_TO_BOTTOM_OF_TASK).build(); + mTransaction.addTaskFragmentOperation(tf1.getFragmentToken(), operation); + assertApplyTransactionAllowed(mTransaction); + + // Ensure correct order of the children after the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf0, task.getChildAt(2).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(1).asActivityRecord()); + assertEquals(tf1, task.getChildAt(0).asTaskFragment()); + } + + @Test + public void testApplyTransaction_reorderToTopOfTask() { + mController.unregisterOrganizer(mIOrganizer); + mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); + final Task task = createTask(mDisplayContent); + // Create a non-embedded Activity at the bottom. + final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + final TaskFragment tf0 = createTaskFragment(task); + final TaskFragment tf1 = createTaskFragment(task); + // Create a non-embedded Activity at the top. + final ActivityRecord topActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + + // Ensure correct order of the children before the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + + // Reorder TaskFragment to top + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_REORDER_TO_TOP_OF_TASK).build(); + mTransaction.addTaskFragmentOperation(tf0.getFragmentToken(), operation); + assertApplyTransactionAllowed(mTransaction); + + // Ensure correct order of the children after the operation + assertEquals(tf0, task.getChildAt(3).asTaskFragment()); + assertEquals(topActivity, task.getChildAt(2).asActivityRecord()); + assertEquals(tf1, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + } + + @Test + public void testApplyTransaction_reorderToBottomOfTask_failsIfNotSystemOrganizer() { + testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( + OP_TYPE_REORDER_TO_BOTTOM_OF_TASK); + } + + @Test + public void testApplyTransaction_reorderToTopOfTask_failsIfNotSystemOrganizer() { + testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( + OP_TYPE_REORDER_TO_TOP_OF_TASK); + } + + private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( + @TaskFragmentOperation.OperationType int opType) { + final Task task = createTask(mDisplayContent); + // Create a non-embedded Activity at the bottom. + final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + final TaskFragment tf0 = createTaskFragment(task); + final TaskFragment tf1 = createTaskFragment(task); + // Create a non-embedded Activity at the top. + final ActivityRecord topActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + + // Ensure correct order of the children before the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + + // Apply reorder transaction, which is expected to fail for non-system organizer. + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + opType).build(); + mTransaction + .addTaskFragmentOperation(tf0.getFragmentToken(), operation) + .setErrorCallbackToken(mErrorToken); + assertApplyTransactionAllowed(mTransaction); + // The pending event will be dispatched on the handler (from requestTraversal). + waitHandlerIdle(mWm.mAnimationHandler); + + assertTaskFragmentErrorTransaction(opType, SecurityException.class); + + // Ensure no change to the order of the children after the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + } + /** * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls * {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the @@ -1782,6 +1905,19 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertEquals(activityToken, change.getActivityToken()); } + /** Setups an embedded TaskFragment. */ + private TaskFragment createTaskFragment(Task task) { + final IBinder token = new Binder(); + TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setFragmentToken(token) + .setOrganizer(mOrganizer) + .createActivityCount(1) + .build(); + mWindowOrganizerController.mLaunchTaskFragments.put(token, taskFragment); + return taskFragment; + } + /** Setups an embedded TaskFragment in a PIP Task. */ private void setupTaskFragmentInPip() { mTaskFragment = new TaskFragmentBuilder(mAtm) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index e86fc366a631..eaeb8049b81a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -100,6 +100,9 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.compatibility.common.util.AdoptShellPermissionsRule; import com.android.internal.os.IResultReceiver; import com.android.server.LocalServices; +import com.android.server.wm.WindowManagerService.WindowContainerInfo; + +import com.google.common.truth.Expect; import org.junit.Rule; import org.junit.Test; @@ -125,6 +128,9 @@ public class WindowManagerServiceTests extends WindowTestsBase { InstrumentationRegistry.getInstrumentation().getUiAutomation(), ADD_TRUSTED_DISPLAY); + @Rule + public Expect mExpect = Expect.create(); + @Test public void testIsRequestedOrientationMapped() { mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true, @@ -674,64 +680,68 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() { - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(null); - assertThat(wct).isNull(); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(null); + assertThat(wci).isNull(); } @Test public void testGetTaskWindowContainerTokenForLaunchCookie_invalidCookie() { Binder cookie = new Binder("test cookie"); - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie); - assertThat(wct).isNull(); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); + assertThat(wci).isNull(); final ActivityRecord testActivity = new ActivityBuilder(mAtm) .setCreateTask(true) .build(); - wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie); - assertThat(wct).isNull(); + wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); + assertThat(wci).isNull(); } @Test public void testGetTaskWindowContainerTokenForLaunchCookie_validCookie() { final Binder cookie = new Binder("ginger cookie"); final WindowContainerToken launchRootTask = mock(WindowContainerToken.class); - setupActivityWithLaunchCookie(cookie, launchRootTask); + final int uid = 123; + setupActivityWithLaunchCookie(cookie, launchRootTask, uid); - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie); - assertThat(wct).isEqualTo(launchRootTask); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); + mExpect.that(wci.getToken()).isEqualTo(launchRootTask); + mExpect.that(wci.getUid()).isEqualTo(uid); } @Test public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies() { final Binder cookie1 = new Binder("ginger cookie"); final WindowContainerToken launchRootTask1 = mock(WindowContainerToken.class); - setupActivityWithLaunchCookie(cookie1, launchRootTask1); + final int uid1 = 123; + setupActivityWithLaunchCookie(cookie1, launchRootTask1, uid1); setupActivityWithLaunchCookie(new Binder("choc chip cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 456); setupActivityWithLaunchCookie(new Binder("peanut butter cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 789); - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie1); - assertThat(wct).isEqualTo(launchRootTask1); + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie1); + mExpect.that(wci.getToken()).isEqualTo(launchRootTask1); + mExpect.that(wci.getUid()).isEqualTo(uid1); } @Test public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies_noneValid() { setupActivityWithLaunchCookie(new Binder("ginger cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 123); setupActivityWithLaunchCookie(new Binder("choc chip cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 456); setupActivityWithLaunchCookie(new Binder("peanut butter cookie"), - mock(WindowContainerToken.class)); + mock(WindowContainerToken.class), /* uid= */ 789); - WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie( + WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie( new Binder("some other cookie")); - assertThat(wct).isNull(); + assertThat(wci).isNull(); } @Test @@ -778,17 +788,18 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test - public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() { + public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerInfo() { WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); Task task = createTask(mDefaultDisplay); ActivityRecord activityRecord = createActivityRecord(task); - ContentRecordingSession session = ContentRecordingSession.createTaskSession( - activityRecord.mLaunchCookie); + ContentRecordingSession session = + ContentRecordingSession.createTaskSession(activityRecord.mLaunchCookie); wmInternal.setContentRecordingSession(session); - assertThat(session.getTokenToRecord()).isEqualTo( - task.mRemoteToken.toWindowContainerToken().asBinder()); + mExpect.that(session.getTokenToRecord()) + .isEqualTo(task.mRemoteToken.toWindowContainerToken().asBinder()); + mExpect.that(session.getTargetUid()).isEqualTo(activityRecord.getUid()); } @Test @@ -1010,12 +1021,12 @@ public class WindowManagerServiceTests extends WindowTestsBase { } } - private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) { + private void setupActivityWithLaunchCookie( + IBinder launchCookie, WindowContainerToken wct, int uid) { final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class); when(remoteToken.toWindowContainerToken()).thenReturn(wct); - final ActivityRecord testActivity = new ActivityBuilder(mAtm) - .setCreateTask(true) - .build(); + final ActivityRecord testActivity = + new ActivityBuilder(mAtm).setCreateTask(true).setUid(uid).build(); testActivity.mLaunchCookie = launchCookie; testActivity.getTask().mRemoteToken = remoteToken; } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 7168670f9652..0b77fd828745 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -40,6 +40,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.wm.testing.Assert.assertThrows; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowContainer.SYNC_STATE_READY; @@ -58,6 +59,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityOptions; @@ -77,11 +79,13 @@ import android.util.Rational; import android.view.Display; import android.view.SurfaceControl; import android.view.WindowInsets; +import android.window.ITaskFragmentOrganizer; import android.window.ITaskOrganizer; import android.window.IWindowContainerTransactionCallback; import android.window.StartingWindowInfo; import android.window.StartingWindowRemovalInfo; import android.window.TaskAppearedInfo; +import android.window.TaskFragmentOrganizer; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -579,6 +583,87 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Test + public void testTaskFragmentHiddenAndFocusableChanges() { + removeGlobalMinSizeRestriction(); + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); + + final WindowContainerTransaction t = new WindowContainerTransaction(); + final TaskFragmentOrganizer organizer = + createTaskFragmentOrganizer(t, true /* isSystemOrganizer */); + + final IBinder token = new Binder(); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(rootTask) + .setFragmentToken(token) + .setOrganizer(organizer) + .createActivityCount(1) + .build(); + + // Should be visible and focusable initially. + assertTrue(rootTask.shouldBeVisible(null)); + assertTrue(taskFragment.shouldBeVisible(null)); + assertTrue(taskFragment.isFocusable()); + assertTrue(taskFragment.isTopActivityFocusable()); + + // Apply transaction to the TaskFragment hidden and not focusable. + t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true); + t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false); + mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( + t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, + false /* shouldApplyIndependently */); + + // Should be not visible and not focusable after the transaction. + assertFalse(taskFragment.shouldBeVisible(null)); + assertFalse(taskFragment.isFocusable()); + assertFalse(taskFragment.isTopActivityFocusable()); + + // Apply transaction to the TaskFragment not hidden and focusable. + t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), false); + t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), true); + mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( + t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, + false /* shouldApplyIndependently */); + + // Should be visible and focusable after the transaction. + assertTrue(taskFragment.shouldBeVisible(null)); + assertTrue(taskFragment.isFocusable()); + assertTrue(taskFragment.isTopActivityFocusable()); + } + + @Test + public void testTaskFragmentHiddenAndFocusableChanges_throwsWhenNotSystemOrganizer() { + removeGlobalMinSizeRestriction(); + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); + + final WindowContainerTransaction t = new WindowContainerTransaction(); + final TaskFragmentOrganizer organizer = + createTaskFragmentOrganizer(t, false /* isSystemOrganizer */); + + final IBinder token = new Binder(); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(rootTask) + .setFragmentToken(token) + .setOrganizer(organizer) + .createActivityCount(1) + .build(); + + assertTrue(rootTask.shouldBeVisible(null)); + assertTrue(taskFragment.shouldBeVisible(null)); + + t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true); + t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false); + + // Non-system organizers are not allow to update the hidden and focusable states. + assertThrows(SecurityException.class, () -> + mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( + t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, + false /* shouldApplyIndependently */) + ); + } + + @Test public void testContainerTranslucentChanges() { removeGlobalMinSizeRestriction(); final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) @@ -1600,4 +1685,20 @@ public class WindowOrganizerTests extends WindowTestsBase { assertTrue(taskIds.contains(expectedTasks[i].mTaskId)); } } + + @NonNull + private TaskFragmentOrganizer createTaskFragmentOrganizer( + @NonNull WindowContainerTransaction t, boolean isSystemOrganizer) { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final ITaskFragmentOrganizer organizerInterface = + ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()); + mWm.mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController + .registerOrganizerInternal( + ITaskFragmentOrganizer.Stub.asInterface( + organizer.getOrganizerToken().asBinder()), + isSystemOrganizer); + t.setTaskFragmentOrganizer(organizerInterface); + + return organizer; + } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 2e6278d9a75d..55b5d11d938a 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -2127,15 +2127,13 @@ public class UsageStatsService extends SystemService implements } private boolean canReportUsageStats() { - final boolean isSystem = isCallingUidSystem(); - if (!Flags.reportUsageStatsPermission()) { - // If the flag is disabled, do no check for the new permission and instead return - // true only if the calling uid is system since System UID can always report stats. - return isSystem; - } - return isSystem - || getContext().checkCallingPermission(Manifest.permission.REPORT_USAGE_STATS) - == PackageManager.PERMISSION_GRANTED; + if (isCallingUidSystem()) { + // System UID can always report UsageStats + return true; + } + + return getContext().checkCallingPermission(Manifest.permission.REPORT_USAGE_STATS) + == PackageManager.PERMISSION_GRANTED; } private boolean hasObserverPermission() { @@ -2627,9 +2625,12 @@ public class UsageStatsService extends SystemService implements return; } - if (!canReportUsageStats()) { - throw new SecurityException("Only the system or holders of the REPORT_USAGE_STATS" - + " permission are allowed to call reportChooserSelection"); + if (Flags.reportUsageStatsPermission()) { + if (!canReportUsageStats()) { + throw new SecurityException( + "Only the system or holders of the REPORT_USAGE_STATS" + + " permission are allowed to call reportChooserSelection"); + } } // Verify if this package exists before reporting an event for it. @@ -2649,9 +2650,17 @@ public class UsageStatsService extends SystemService implements @Override public void reportUserInteraction(String packageName, int userId) { Objects.requireNonNull(packageName); - if (!canReportUsageStats()) { - throw new SecurityException("Only the system or holders of the REPORT_USAGE_STATS" - + " permission are allowed to call reportUserInteraction"); + if (Flags.reportUsageStatsPermission()) { + if (!canReportUsageStats()) { + throw new SecurityException( + "Only the system or holders of the REPORT_USAGE_STATS" + + " permission are allowed to call reportUserInteraction"); + } + } else { + if (!isCallingUidSystem()) { + throw new SecurityException("Only system is allowed to call" + + " reportUserInteraction"); + } } final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime()); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 138e575a6872..1c689d0d5ce3 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -58,6 +58,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Parcel; import android.os.ParcelFileDescriptor; +import android.os.PermissionEnforcer; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteCallbackList; @@ -67,6 +68,7 @@ import android.os.SharedMemory; import android.os.ShellCallback; import android.os.Trace; import android.os.UserHandle; +import android.permission.flags.Flags; import android.provider.Settings; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback; @@ -1286,6 +1288,17 @@ public class VoiceInteractionManagerService extends SystemService { } } + // Enforce permissions that are flag controlled. The flag value decides if the permission + // should be enforced. + private void initAndVerifyDetector_enforcePermissionWithFlags() { + PermissionEnforcer enforcer = mContext.getSystemService(PermissionEnforcer.class); + if (Flags.voiceActivationPermissionApis()) { + enforcer.enforcePermission( + android.Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO, + getCallingPid(), getCallingUid()); + } + } + @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) @Override public void initAndVerifyDetector( @@ -1295,7 +1308,13 @@ public class VoiceInteractionManagerService extends SystemService { @NonNull IBinder token, IHotwordRecognitionStatusCallback callback, int detectorType) { + // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the + // {@link #initAndVerifyDetector(Identity, PersistableBundle, ShareMemory, IBinder, + // IHotwordRecognitionStatusCallback, int)} + // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully + // launched. super.initAndVerifyDetector_enforcePermission(); + initAndVerifyDetector_enforcePermissionWithFlags(); synchronized (this) { enforceIsCurrentVoiceInteractionService(); diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java index 971fc781a719..e20e4d200251 100644 --- a/telephony/java/android/telephony/BarringInfo.java +++ b/telephony/java/android/telephony/BarringInfo.java @@ -202,6 +202,24 @@ public final class BarringInfo implements Parcelable { && mConditionalBarringTimeSeconds == other.mConditionalBarringTimeSeconds; } + private static String barringTypeToString(@BarringType int barringType) { + return switch (barringType) { + case BARRING_TYPE_NONE -> "NONE"; + case BARRING_TYPE_CONDITIONAL -> "CONDITIONAL"; + case BARRING_TYPE_UNCONDITIONAL -> "UNCONDITIONAL"; + case BARRING_TYPE_UNKNOWN -> "UNKNOWN"; + default -> "UNKNOWN(" + barringType + ")"; + }; + } + + @Override + public String toString() { + return "BarringServiceInfo {mBarringType=" + barringTypeToString(mBarringType) + + ", mIsConditionallyBarred=" + mIsConditionallyBarred + + ", mConditionalBarringFactor=" + mConditionalBarringFactor + + ", mConditionalBarringTimeSeconds=" + mConditionalBarringTimeSeconds + "}"; + } + /** @hide */ public BarringServiceInfo(Parcel p) { mBarringType = p.readInt(); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 90fa69ff8a15..73220c353b36 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -15033,15 +15033,6 @@ public class TelephonyManager { @TestApi public static final int HAL_SERVICE_IMS = 7; - /** - * HAL service type that supports the HAL APIs implementation of IRadioSatellite - * {@link RadioSatelliteProxy} - * @hide - */ - @TestApi - @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) - public static final int HAL_SERVICE_SATELLITE = 8; - /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"HAL_SERVICE_"}, @@ -15054,7 +15045,6 @@ public class TelephonyManager { HAL_SERVICE_SIM, HAL_SERVICE_VOICE, HAL_SERVICE_IMS, - HAL_SERVICE_SATELLITE }) public @interface HalService {} diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 72e4389e0788..a20f26c1807b 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -120,31 +120,6 @@ public interface RILConstants { int BLOCKED_DUE_TO_CALL = 69; /* SMS is blocked due to call control */ int RF_HARDWARE_ISSUE = 70; /* RF HW issue is detected */ int NO_RF_CALIBRATION_INFO = 71; /* No RF calibration in device */ - int ENCODING_NOT_SUPPORTED = 72; /* The encoding scheme is not supported by - either the network or the MS. */ - int FEATURE_NOT_SUPPORTED = 73; /* The requesting feature is not supported by - the service provider. */ - int INVALID_CONTACT = 74; /* The contact to be added is either not - existing or not valid. */ - int MODEM_INCOMPATIBLE = 75; /* The modem of the MS is not compatible with - the service provider. */ - int NETWORK_TIMEOUT = 76; /* Modem timeout to receive ACK or response from - network after sending a request to it. */ - int NO_SATELLITE_SIGNAL = 77; /* Modem fails to communicate with the satellite - network since there is no satellite signal.*/ - int NOT_SUFFICIENT_ACCOUNT_BALANCE = 78; /* The request cannot be performed since the - subscriber's account balance is not - sufficient. */ - int RADIO_TECHNOLOGY_NOT_SUPPORTED = 79; /* The radio technology is not supported by the - service provider. */ - int SUBSCRIBER_NOT_AUTHORIZED = 80; /* The subscription is not authorized to - register with the service provider. */ - int SWITCHED_FROM_SATELLITE_TO_TERRESTRIAL = 81; /* While processing a request from the - Framework the satellite modem detects - terrestrial signal, aborts the request, and - switches to the terrestrial network. */ - int UNIDENTIFIED_SUBSCRIBER = 82; /* The subscriber is not registered with the - service provider */ // Below is list of OEM specific error codes which can by used by OEMs in case they don't want to // reveal particular replacement for Generic failure @@ -568,22 +543,7 @@ public interface RILConstants { int RIL_REQUEST_IS_N1_MODE_ENABLED = 242; int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243; int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244; - int RIL_REQUEST_GET_SATELLITE_CAPABILITIES = 245; - int RIL_REQUEST_SET_SATELLITE_POWER = 246; - int RIL_REQUEST_GET_SATELLITE_POWER = 247; - int RIL_REQUEST_PROVISION_SATELLITE_SERVICE = 248; - int RIL_REQUEST_ADD_ALLOWED_SATELLITE_CONTACTS = 249; - int RIL_REQUEST_REMOVE_ALLOWED_SATELLITE_CONTACTS = 250; - int RIL_REQUEST_SEND_SATELLITE_MESSAGES = 251; - int RIL_REQUEST_GET_PENDING_SATELLITE_MESSAGES = 252; - int RIL_REQUEST_GET_SATELLITE_MODE = 253; - int RIL_REQUEST_SET_SATELLITE_INDICATION_FILTER = 254; - int RIL_REQUEST_START_SENDING_SATELLITE_POINTING_INFO = 255; - int RIL_REQUEST_STOP_SENDING_SATELLITE_POINTING_INFO = 256; - int RIL_REQUEST_GET_MAX_CHARACTERS_PER_SATELLITE_TEXT_MESSAGE = 257; - int RIL_REQUEST_GET_TIME_FOR_NEXT_SATELLITE_VISIBILITY = 258; - int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 259; - int RIL_REQUEST_SET_SATELLITE_PLMN = 260; + int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 245; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; @@ -645,13 +605,6 @@ public interface RILConstants { int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053; int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054; int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055; - int RIL_UNSOL_PENDING_SATELLITE_MESSAGE_COUNT = 1056; - int RIL_UNSOL_NEW_SATELLITE_MESSAGES = 1057; - int RIL_UNSOL_SATELLITE_MESSAGES_TRANSFER_COMPLETE = 1058; - int RIL_UNSOL_SATELLITE_POINTING_INFO_CHANGED = 1059; - int RIL_UNSOL_SATELLITE_MODE_CHANGED = 1060; - int RIL_UNSOL_SATELLITE_RADIO_TECHNOLOGY_CHANGED = 1061; - int RIL_UNSOL_SATELLITE_PROVISION_STATE_CHANGED = 1062; /* The following unsols are not defined in RIL.h */ int RIL_UNSOL_HAL_NON_RIL_BASE = 1100; diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt index 359845dc0de6..47d6d235848f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt @@ -36,7 +36,8 @@ import org.junit.runners.Parameterized /** * Test launching a secondary Activity into Picture-In-Picture mode. * - * Setup: Start from a split A|B. Transition: B enters PIP, observe the window shrink to the bottom + * Setup: Start from a split A|B. + * Transition: B enters PIP, observe the window first goes fullscreen then shrink to the bottom * right corner on screen. * * To run this test: `atest FlickerTestsOther:SecondaryActivityEnterPipTest` @@ -63,7 +64,16 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) : } } - /** Main and secondary activity start from a split each taking half of the screen. */ + /** + * We expect the background layer to be visible during this transition. + */ + @Presubmit + @Test + override fun backgroundLayerNeverVisible(): Unit {} + + /** + * Main and secondary activity start from a split each taking half of the screen. + */ @Presubmit @Test fun layersStartFromEqualSplit() { @@ -109,7 +119,7 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) : .isVisible(TRANSITION_SNAPSHOT) .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) .then() - .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT, isOptional = true) } flicker.assertLayersEnd { visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp index 0599c1d01906..226e2fadf735 100644 --- a/tools/hoststubgen/hoststubgen/Android.bp +++ b/tools/hoststubgen/hoststubgen/Android.bp @@ -7,9 +7,38 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +// Visibility only for ravenwood prototype uses. +genrule_defaults { + name: "hoststubgen-for-prototype-only-genrule", + visibility: [ + ":__subpackages__", + "//frameworks/base/ravenwood:__subpackages__", + ], +} + +// Visibility only for ravenwood prototype uses. +java_defaults { + name: "hoststubgen-for-prototype-only-java", + visibility: [ + ":__subpackages__", + "//frameworks/base/ravenwood:__subpackages__", + ], +} + +// Visibility only for ravenwood prototype uses. +filegroup_defaults { + name: "hoststubgen-for-prototype-only-filegroup", + visibility: [ + ":__subpackages__", + "//frameworks/base/ravenwood:__subpackages__", + ], +} + // This library contains the standard hoststubgen annotations. +// This is only for the prototype. The productionized version is "ravenwood-annotations". java_library { name: "hoststubgen-annotations", + defaults: ["hoststubgen-for-prototype-only-java"], srcs: [ "annotations-src/**/*.java", ], @@ -18,7 +47,6 @@ java_library { // Seems like we need it to avoid circular deps. // Copied it from "app-compat-annotations". sdk_version: "core_current", - visibility: ["//visibility:public"], } // This library contains helper classes used in the host side test environment at runtime. @@ -55,12 +83,13 @@ java_binary_host { } // File that contains the standard command line argumetns to hoststubgen. +// This is only for the prototype. The productionized version is "ravenwood-standard-options". filegroup { name: "hoststubgen-standard-options", + defaults: ["hoststubgen-for-prototype-only-filegroup"], srcs: [ "hoststubgen-standard-options.txt", ], - visibility: ["//visibility:public"], } hoststubgen_common_options = "$(location hoststubgen) " + @@ -93,7 +122,6 @@ genrule_defaults { "hoststubgen_keep_all.txt", "hoststubgen_dump.txt", ], - // visibility: ["//visibility:public"], } // Generate the stub/impl from framework-all, with hidden APIs. @@ -111,8 +139,10 @@ java_genrule_host { } // Extract the stub jar from "framework-all-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-hidden-api-host-stub", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-all-hidden-api-host{host_stub.jar}", @@ -120,12 +150,13 @@ java_genrule_host { out: [ "host_stub.jar", ], - visibility: ["//visibility:public"], } // Extract the impl jar from "framework-all-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-hidden-api-host-impl", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-all-hidden-api-host{host_impl.jar}", @@ -133,11 +164,11 @@ java_genrule_host { out: [ "host_impl.jar", ], - visibility: ["//visibility:public"], } // Generate the stub/impl from framework-all, with only public/system/test APIs, without // hidden APIs. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-test-api-host", defaults: ["hoststubgen-command-defaults"], @@ -154,8 +185,10 @@ java_genrule_host { } // Extract the stub jar from "framework-all-test-api-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-test-api-host-stub", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-all-test-api-host{host_stub.jar}", @@ -163,12 +196,13 @@ java_genrule_host { out: [ "host_stub.jar", ], - visibility: ["//visibility:public"], } // Extract the impl jar from "framework-all-test-api-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules. java_genrule_host { name: "framework-all-test-api-host-impl", + defaults: ["hoststubgen-for-prototype-only-genrule"], cmd: "cp $(in) $(out)", srcs: [ ":framework-all-test-api-host{host_impl.jar}", @@ -176,7 +210,6 @@ java_genrule_host { out: [ "host_impl.jar", ], - visibility: ["//visibility:public"], } // This library contains helper classes to build hostside tests/targets. @@ -186,6 +219,7 @@ java_genrule_host { // Ideally this library should be empty. java_library_host { name: "hoststubgen-helper-framework-buildtime", + defaults: ["hoststubgen-for-prototype-only-java"], srcs: [ "helper-framework-buildtime-src/**/*.java", ], @@ -195,13 +229,13 @@ java_library_host { "framework-all-hidden-api-host-impl", "junit", ], - visibility: ["//visibility:public"], } // This module contains "fake" libcore/dalvik classes, framework native substitution, etc, // that are needed at runtime. java_library_host { name: "hoststubgen-helper-framework-runtime", + defaults: ["hoststubgen-for-prototype-only-java"], srcs: [ "helper-framework-runtime-src/**/*.java", ], @@ -209,7 +243,6 @@ java_library_host { "hoststubgen-helper-runtime", "framework-all-hidden-api-host-impl", ], - visibility: ["//visibility:public"], } // Defaults for host side test modules. |